mirror of
https://github.com/lalbornoz/roar.git
synced 2024-12-22 20:36:37 +00:00
asciiblaster-cordoba/: preliminarily added for standalone Android app support via Apache Córdoba.
.gitignore: adds asciiblaster-cordoba/{node_modules,platforms/android/{.gradle,app/build,CordovaLib/build}}.
This commit is contained in:
parent
b853878b4b
commit
80cacedd89
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
||||
*.sw[op]
|
||||
asciiblaster-cordoba/node_modules
|
||||
asciiblaster-cordoba/platforms/android/.gradle
|
||||
asciiblaster-cordoba/platforms/android/app/build
|
||||
asciiblaster-cordoba/platforms/android/CordovaLib/build
|
||||
asciiblaster-nw/releases/*
|
||||
|
27
asciiblaster-cordoba/config.xml
Normal file
27
asciiblaster-cordoba/config.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget id="com.lalbornoz.asciiblaster" version="1.0.3" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<author email="lucio@lucioillanes.de" href="https://github.com/lalbornoz/asciiblaster#readme">
|
||||
Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>, based on work by JOLLO NET NA
|
||||
</author>
|
||||
<description>
|
||||
asciiblaster (standalone Apache Córdoba app)
|
||||
</description>
|
||||
<name>asciiblaster-cordoba</name>
|
||||
<access origin="*" />
|
||||
<content src="www/index.html" />
|
||||
<plugin name="cordova-plugin-whitelist" spec="1" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<platform name="android">
|
||||
<allow-intent href="market:*" />
|
||||
</platform>
|
||||
<platform name="ios">
|
||||
<allow-intent href="itms:*" />
|
||||
<allow-intent href="itms-apps:*" />
|
||||
</platform>
|
||||
<engine name="android" spec="~7.1.3" />
|
||||
</widget>
|
218
asciiblaster-cordoba/package-lock.json
generated
Normal file
218
asciiblaster-cordoba/package-lock.json
generated
Normal file
@ -0,0 +1,218 @@
|
||||
{
|
||||
"name": "asciiblaster-cordoba",
|
||||
"version": "1.0.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"cordova-android": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-7.1.3.tgz",
|
||||
"integrity": "sha512-nXGtnMS17EX+AIJllHflj3lwcYKl00KwM9No/6IINhOs0gJ5hAg1I2JlCzDlkUiWwSr2nmIOAYGeQmxnkP2s2Q==",
|
||||
"requires": {
|
||||
"abbrev": "*",
|
||||
"android-versions": "1.3.0",
|
||||
"ansi": "*",
|
||||
"balanced-match": "*",
|
||||
"base64-js": "1.2.0",
|
||||
"big-integer": "1.6.32",
|
||||
"bplist-parser": "*",
|
||||
"brace-expansion": "*",
|
||||
"concat-map": "*",
|
||||
"cordova-common": "2.2.5",
|
||||
"cordova-registry-mapper": "*",
|
||||
"elementtree": "0.1.6",
|
||||
"glob": "5.0.15",
|
||||
"inflight": "*",
|
||||
"inherits": "*",
|
||||
"minimatch": "*",
|
||||
"nopt": "3.0.1",
|
||||
"once": "*",
|
||||
"path-is-absolute": "1.0.1",
|
||||
"plist": "2.1.0",
|
||||
"properties-parser": "0.2.3",
|
||||
"q": "1.4.1",
|
||||
"sax": "0.3.5",
|
||||
"semver": "5.5.0",
|
||||
"shelljs": "0.5.3",
|
||||
"underscore": "*",
|
||||
"unorm": "*",
|
||||
"wrappy": "*",
|
||||
"xmlbuilder": "8.2.2",
|
||||
"xmldom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"android-versions": {
|
||||
"version": "1.3.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"semver": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"ansi": {
|
||||
"version": "0.3.1",
|
||||
"bundled": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.32",
|
||||
"bundled": true
|
||||
},
|
||||
"bplist-parser": {
|
||||
"version": "0.1.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"big-integer": "^1.6.7"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"cordova-common": {
|
||||
"version": "2.2.5",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ansi": "^0.3.1",
|
||||
"bplist-parser": "^0.1.0",
|
||||
"cordova-registry-mapper": "^1.1.8",
|
||||
"elementtree": "0.1.6",
|
||||
"glob": "^5.0.13",
|
||||
"minimatch": "^3.0.0",
|
||||
"plist": "^2.1.0",
|
||||
"q": "^1.4.1",
|
||||
"shelljs": "^0.5.3",
|
||||
"underscore": "^1.8.3",
|
||||
"unorm": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"cordova-registry-mapper": {
|
||||
"version": "1.1.15",
|
||||
"bundled": true
|
||||
},
|
||||
"elementtree": {
|
||||
"version": "0.1.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"sax": "0.3.5"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "5.0.15",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "2.1.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"base64-js": "1.2.0",
|
||||
"xmlbuilder": "8.2.2",
|
||||
"xmldom": "0.1.x"
|
||||
}
|
||||
},
|
||||
"properties-parser": {
|
||||
"version": "0.2.3",
|
||||
"bundled": true
|
||||
},
|
||||
"q": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "0.3.5",
|
||||
"bundled": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"bundled": true
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.5.3",
|
||||
"bundled": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.9.1",
|
||||
"bundled": true
|
||||
},
|
||||
"unorm": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "8.2.2",
|
||||
"bundled": true
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.1.27",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
asciiblaster-cordoba/package.json
Normal file
34
asciiblaster-cordoba/package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"author": "Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>, based on work by JOLLO NET NA",
|
||||
"bugs": {
|
||||
"url": "https://github.com/lalbornoz/asciiblaster/issues"
|
||||
},
|
||||
"cordova": {
|
||||
"plugins": {
|
||||
"cordova-plugin-whitelist": {}
|
||||
},
|
||||
"platforms": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"cordova-android": "^7.1.3",
|
||||
"cordova-plugin-whitelist": "^1.3.3"
|
||||
},
|
||||
"description": "asciiblaster (standalone Apache Córdoba app)",
|
||||
"directories": {
|
||||
"www": "www"
|
||||
},
|
||||
"homepage": "https://github.com/lalbornoz/asciiblaster#readme",
|
||||
"license": "Jollo LNT license <https://raw.githubusercontent.com/lalbornoz/asciiblaster/master/LICENCE>",
|
||||
"main": "www/index.html",
|
||||
"name": "asciiblaster-cordoba",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lalbornoz/asciiblaster.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"version": "1.0.3"
|
||||
}
|
14
asciiblaster-cordoba/platforms/android/.gitignore
vendored
Normal file
14
asciiblaster-cordoba/platforms/android/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Non-project-specific build files:
|
||||
build.xml
|
||||
local.properties
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/gradle
|
||||
# Ant builds
|
||||
ant-build
|
||||
ant-gen
|
||||
# Eclipse builds
|
||||
gen
|
||||
out
|
||||
# Gradle builds
|
||||
/build
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
</manifest>
|
137
asciiblaster-cordoba/platforms/android/CordovaLib/build.gradle
Normal file
137
asciiblaster-cordoba/platforms/android/CordovaLib/build.gradle
Normal file
@ -0,0 +1,137 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
ext {
|
||||
apply from: 'cordova.gradle'
|
||||
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
group = 'org.apache.cordova'
|
||||
version = '7.1.3'
|
||||
|
||||
android {
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
publishNonDefault true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
}
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
pom {
|
||||
project {
|
||||
packaging 'aar'
|
||||
name 'Cordova'
|
||||
url 'https://cordova.apache.org'
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id 'stevengill'
|
||||
name 'Steve Gill'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
|
||||
developerConnection 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
|
||||
url 'https://git-wip-us.apache.org/repos/asf?p=cordova-android'
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
bintray {
|
||||
user = System.getenv('BINTRAY_USER')
|
||||
key = System.getenv('BINTRAY_KEY')
|
||||
configurations = ['archives']
|
||||
pkg {
|
||||
repo = 'maven'
|
||||
name = 'cordova-android'
|
||||
userOrg = 'cordova'
|
||||
licenses = ['Apache-2.0']
|
||||
vcsUrl = 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git'
|
||||
websiteUrl = 'https://cordova.apache.org'
|
||||
issueTrackerUrl = 'https://issues.apache.org/jira/browse/CB'
|
||||
publicDownloadNumbers = true
|
||||
licenses = ['Apache-2.0']
|
||||
labels = ['android', 'cordova', 'phonegap']
|
||||
version {
|
||||
name = '7.1.3'
|
||||
released = new Date()
|
||||
vcsTag = '7.1.3'
|
||||
}
|
||||
}
|
||||
}
|
205
asciiblaster-cordoba/platforms/android/CordovaLib/cordova.gradle
Normal file
205
asciiblaster-cordoba/platforms/android/CordovaLib/cordova.gradle
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import groovy.swing.SwingBuilder
|
||||
|
||||
String doEnsureValueExists(filePath, props, key) {
|
||||
if (props.get(key) == null) {
|
||||
throw new GradleException(filePath + ': Missing key required "' + key + '"')
|
||||
}
|
||||
return props.get(key)
|
||||
}
|
||||
|
||||
String doGetProjectTarget() {
|
||||
def props = new Properties()
|
||||
def propertiesFile = 'project.properties';
|
||||
if(!(file(propertiesFile).exists())) {
|
||||
propertiesFile = '../project.properties';
|
||||
}
|
||||
file(propertiesFile).withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
return doEnsureValueExists('project.properties', props, 'target')
|
||||
}
|
||||
|
||||
String[] getAvailableBuildTools() {
|
||||
def buildToolsDir = new File(getAndroidSdkDir(), "build-tools")
|
||||
buildToolsDir.list()
|
||||
.findAll { it ==~ /[0-9.]+/ }
|
||||
.sort { a, b -> compareVersions(b, a) }
|
||||
}
|
||||
|
||||
String doFindLatestInstalledBuildTools(String minBuildToolsVersion) {
|
||||
def availableBuildToolsVersions
|
||||
try {
|
||||
availableBuildToolsVersions = getAvailableBuildTools()
|
||||
} catch (e) {
|
||||
println "An exception occurred while trying to find the Android build tools."
|
||||
throw e
|
||||
}
|
||||
if (availableBuildToolsVersions.length > 0) {
|
||||
def highestBuildToolsVersion = availableBuildToolsVersions[0]
|
||||
if (compareVersions(highestBuildToolsVersion, minBuildToolsVersion) < 0) {
|
||||
throw new RuntimeException(
|
||||
"No usable Android build tools found. Highest installed version is " +
|
||||
highestBuildToolsVersion + "; minimum version required is " +
|
||||
minBuildToolsVersion + ".")
|
||||
}
|
||||
highestBuildToolsVersion
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"No installed build tools found. Install the Android build tools version " +
|
||||
minBuildToolsVersion + " or higher.")
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first non-zero result of subtracting version list elements
|
||||
// pairwise. If they are all identical, return the difference in length of
|
||||
// the two lists.
|
||||
int compareVersionList(Collection aParts, Collection bParts) {
|
||||
def pairs = ([aParts, bParts]).transpose()
|
||||
pairs.findResult(aParts.size()-bParts.size()) {it[0] - it[1] != 0 ? it[0] - it[1] : null}
|
||||
}
|
||||
|
||||
// Compare two version strings, such as "19.0.0" and "18.1.1.0". If all matched
|
||||
// elements are identical, the longer version is the largest by this method.
|
||||
// Examples:
|
||||
// "19.0.0" > "19"
|
||||
// "19.0.1" > "19.0.0"
|
||||
// "19.1.0" > "19.0.1"
|
||||
// "19" > "18.999.999"
|
||||
int compareVersions(String a, String b) {
|
||||
def aParts = a.tokenize('.').collect {it.toInteger()}
|
||||
def bParts = b.tokenize('.').collect {it.toInteger()}
|
||||
compareVersionList(aParts, bParts)
|
||||
}
|
||||
|
||||
String getAndroidSdkDir() {
|
||||
def rootDir = project.rootDir
|
||||
def androidSdkDir = null
|
||||
String envVar = System.getenv("ANDROID_HOME")
|
||||
def localProperties = new File(rootDir, 'local.properties')
|
||||
String systemProperty = System.getProperty("android.home")
|
||||
if (envVar != null) {
|
||||
androidSdkDir = envVar
|
||||
} else if (localProperties.exists()) {
|
||||
Properties properties = new Properties()
|
||||
localProperties.withInputStream { instr ->
|
||||
properties.load(instr)
|
||||
}
|
||||
def sdkDirProp = properties.getProperty('sdk.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = sdkDirProp
|
||||
} else {
|
||||
sdkDirProp = properties.getProperty('android.dir')
|
||||
if (sdkDirProp != null) {
|
||||
androidSdkDir = (new File(rootDir, sdkDirProp)).getAbsolutePath()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (androidSdkDir == null && systemProperty != null) {
|
||||
androidSdkDir = systemProperty
|
||||
}
|
||||
if (androidSdkDir == null) {
|
||||
throw new RuntimeException(
|
||||
"Unable to determine Android SDK directory.")
|
||||
}
|
||||
androidSdkDir
|
||||
}
|
||||
|
||||
def doExtractIntFromManifest(name) {
|
||||
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||
def pattern = Pattern.compile(name + "=\"(\\d+)\"")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
matcher.find()
|
||||
return new BigInteger(matcher.group(1))
|
||||
}
|
||||
|
||||
def doExtractStringFromManifest(name) {
|
||||
def manifestFile = file(android.sourceSets.main.manifest.srcFile)
|
||||
def pattern = Pattern.compile(name + "=\"(\\S+)\"")
|
||||
def matcher = pattern.matcher(manifestFile.getText())
|
||||
matcher.find()
|
||||
return matcher.group(1)
|
||||
}
|
||||
|
||||
def doPromptForPassword(msg) {
|
||||
if (System.console() == null) {
|
||||
def ret = null
|
||||
new SwingBuilder().edt {
|
||||
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
|
||||
vbox {
|
||||
label(text: msg)
|
||||
def input = passwordField()
|
||||
button(defaultButton: true, text: 'OK', actionPerformed: {
|
||||
ret = input.password;
|
||||
dispose();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
throw new GradleException('User canceled build')
|
||||
}
|
||||
return new String(ret)
|
||||
} else {
|
||||
return System.console().readPassword('\n' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
def doGetConfigXml() {
|
||||
def xml = file("src/main/res/xml/config.xml").getText()
|
||||
// Disable namespace awareness since Cordova doesn't use them properly
|
||||
return new XmlParser(false, false).parseText(xml)
|
||||
}
|
||||
|
||||
def doGetConfigPreference(name, defaultValue) {
|
||||
name = name.toLowerCase()
|
||||
def root = doGetConfigXml()
|
||||
|
||||
def ret = defaultValue
|
||||
root.preference.each { it ->
|
||||
def attrName = it.attribute("name")
|
||||
if (attrName && attrName.toLowerCase() == name) {
|
||||
ret = it.attribute("value")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Properties exported here are visible to all plugins.
|
||||
ext {
|
||||
// These helpers are shared, but are not guaranteed to be stable / unchanged.
|
||||
privateHelpers = {}
|
||||
privateHelpers.getProjectTarget = { doGetProjectTarget() }
|
||||
privateHelpers.findLatestInstalledBuildTools = { doFindLatestInstalledBuildTools('19.1.0') }
|
||||
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
|
||||
privateHelpers.extractStringFromManifest = { name -> doExtractStringFromManifest(name) }
|
||||
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
|
||||
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
|
||||
|
||||
// These helpers can be used by plugins / projects and will not change.
|
||||
cdvHelpers = {}
|
||||
// Returns a XmlParser for the config.xml. Added in 4.1.0.
|
||||
cdvHelpers.getConfigXml = { doGetConfigXml() }
|
||||
// Returns the value for the desired <preference>. Added in 4.1.0.
|
||||
cdvHelpers.getConfigPreference = { name, defaultValue -> doGetConfigPreference(name, defaultValue) }
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system use,
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
|
||||
# Indicates whether an apk should be generated for each density.
|
||||
split.density=false
|
||||
# Project target.
|
||||
target=android-27
|
||||
apk-configurations=
|
||||
renderscript.opt.level=O0
|
||||
android.library=true
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
/**
|
||||
* The Class AuthenticationToken defines the userName and password to be used for authenticating a web resource
|
||||
*/
|
||||
public class AuthenticationToken {
|
||||
private String userName;
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Gets the user name.
|
||||
*
|
||||
* @return the user name
|
||||
*/
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user name.
|
||||
*
|
||||
* @param userName
|
||||
* the new user name
|
||||
*/
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the password.
|
||||
*
|
||||
* @return the password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password.
|
||||
*
|
||||
* @param password
|
||||
* the new password
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
/*
|
||||
* This is a utility class that allows us to get the BuildConfig variable, which is required
|
||||
* for the use of different providers. This is not guaranteed to work, and it's better for this
|
||||
* to be set in the build step in config.xml
|
||||
*
|
||||
*/
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
public class BuildHelper {
|
||||
|
||||
|
||||
private static String TAG="BuildHelper";
|
||||
|
||||
/*
|
||||
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
|
||||
* that read the Build Configuration.
|
||||
*
|
||||
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
|
||||
* StackOverflow. This is annoying as hell! However, this method does not work with
|
||||
* ProGuard, and you should use the config.xml to define the application_id
|
||||
*
|
||||
*/
|
||||
|
||||
public static Object getBuildConfigValue(Context ctx, String key)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
|
||||
Field field = clazz.getField(key);
|
||||
return field.get(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.PluginResult;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CallbackContext {
|
||||
private static final String LOG_TAG = "CordovaPlugin";
|
||||
|
||||
private String callbackId;
|
||||
private CordovaWebView webView;
|
||||
protected boolean finished;
|
||||
private int changingThreads;
|
||||
|
||||
public CallbackContext(String callbackId, CordovaWebView webView) {
|
||||
this.callbackId = callbackId;
|
||||
this.webView = webView;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public boolean isChangingThreads() {
|
||||
return changingThreads > 0;
|
||||
}
|
||||
|
||||
public String getCallbackId() {
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
public void sendPluginResult(PluginResult pluginResult) {
|
||||
synchronized (this) {
|
||||
if (finished) {
|
||||
LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
|
||||
return;
|
||||
} else {
|
||||
finished = !pluginResult.getKeepCallback();
|
||||
}
|
||||
}
|
||||
webView.sendPluginResult(pluginResult, callbackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(JSONObject message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(String message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(JSONArray message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(byte[] message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*
|
||||
* @param message The message to add to the success result.
|
||||
*/
|
||||
public void success(int message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for success callbacks that just returns the Status.OK by default
|
||||
*/
|
||||
public void success() {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.OK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||
*
|
||||
* @param message The message to add to the error result.
|
||||
*/
|
||||
public void error(JSONObject message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||
*
|
||||
* @param message The message to add to the error result.
|
||||
*/
|
||||
public void error(String message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for error callbacks that just returns the Status.ERROR by default
|
||||
*
|
||||
* @param message The message to add to the error result.
|
||||
*/
|
||||
public void error(int message) {
|
||||
sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message));
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
* Provides a collection that maps unique request codes to CordovaPlugins and Integers.
|
||||
* Used to ensure that when plugins make requests for runtime permissions, those requests do not
|
||||
* collide with requests from other plugins that use the same request code value.
|
||||
*/
|
||||
public class CallbackMap {
|
||||
private int currentCallbackId = 0;
|
||||
private SparseArray<Pair<CordovaPlugin, Integer>> callbacks;
|
||||
|
||||
public CallbackMap() {
|
||||
this.callbacks = new SparseArray<Pair<CordovaPlugin, Integer>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a CordovaPlugin and request code and returns a new unique request code to use
|
||||
* in a permission request.
|
||||
*
|
||||
* @param receiver The plugin that is making the request
|
||||
* @param requestCode The original request code used by the plugin
|
||||
* @return A unique request code that can be used to retrieve this callback
|
||||
* with getAndRemoveCallback()
|
||||
*/
|
||||
public synchronized int registerCallback(CordovaPlugin receiver, int requestCode) {
|
||||
int mappedId = this.currentCallbackId++;
|
||||
callbacks.put(mappedId, new Pair<CordovaPlugin, Integer>(receiver, requestCode));
|
||||
return mappedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and removes a callback stored in the map using the mapped request code
|
||||
* obtained from registerCallback()
|
||||
*
|
||||
* @param mappedId The request code obtained from registerCallback()
|
||||
* @return The CordovaPlugin and orignal request code that correspond to the
|
||||
* given mappedCode
|
||||
*/
|
||||
public synchronized Pair<CordovaPlugin, Integer> getAndRemoveCallback(int mappedId) {
|
||||
Pair<CordovaPlugin, Integer> callback = callbacks.get(mappedId);
|
||||
callbacks.remove(mappedId);
|
||||
return callback;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
|
||||
public class Config {
|
||||
private static final String TAG = "Config";
|
||||
|
||||
static ConfigXmlParser parser;
|
||||
|
||||
private Config() {
|
||||
}
|
||||
|
||||
public static void init(Activity action) {
|
||||
parser = new ConfigXmlParser();
|
||||
parser.parse(action);
|
||||
//TODO: Add feature to bring this back. Some preferences should be overridden by intents, but not all
|
||||
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
|
||||
}
|
||||
|
||||
// Intended to be used for testing only; creates an empty configuration.
|
||||
public static void init() {
|
||||
if (parser == null) {
|
||||
parser = new ConfigXmlParser();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getStartUrl() {
|
||||
if (parser == null) {
|
||||
return "file:///android_asset/www/index.html";
|
||||
}
|
||||
return parser.getLaunchUrl();
|
||||
}
|
||||
|
||||
public static String getErrorUrl() {
|
||||
return parser.getPreferences().getString("errorurl", null);
|
||||
}
|
||||
|
||||
public static List<PluginEntry> getPluginEntries() {
|
||||
return parser.getPluginEntries();
|
||||
}
|
||||
|
||||
public static CordovaPreferences getPreferences() {
|
||||
return parser.getPreferences();
|
||||
}
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return parser != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class ConfigXmlParser {
|
||||
private static String TAG = "ConfigXmlParser";
|
||||
|
||||
private String launchUrl = "file:///android_asset/www/index.html";
|
||||
private CordovaPreferences prefs = new CordovaPreferences();
|
||||
private ArrayList<PluginEntry> pluginEntries = new ArrayList<PluginEntry>(20);
|
||||
|
||||
public CordovaPreferences getPreferences() {
|
||||
return prefs;
|
||||
}
|
||||
|
||||
public ArrayList<PluginEntry> getPluginEntries() {
|
||||
return pluginEntries;
|
||||
}
|
||||
|
||||
public String getLaunchUrl() {
|
||||
return launchUrl;
|
||||
}
|
||||
|
||||
public void parse(Context action) {
|
||||
// First checking the class namespace for config.xml
|
||||
int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
|
||||
if (id == 0) {
|
||||
// If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
|
||||
id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
|
||||
if (id == 0) {
|
||||
LOG.e(TAG, "res/xml/config.xml is missing!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
parse(action.getResources().getXml(id));
|
||||
}
|
||||
|
||||
boolean insideFeature = false;
|
||||
String service = "", pluginClass = "", paramType = "";
|
||||
boolean onload = false;
|
||||
|
||||
public void parse(XmlPullParser xml) {
|
||||
int eventType = -1;
|
||||
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
handleStartTag(xml);
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG)
|
||||
{
|
||||
handleEndTag(xml);
|
||||
}
|
||||
try {
|
||||
eventType = xml.next();
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleStartTag(XmlPullParser xml) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("feature")) {
|
||||
//Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
|
||||
//Set the bit for reading params
|
||||
insideFeature = true;
|
||||
service = xml.getAttributeValue(null, "name");
|
||||
}
|
||||
else if (insideFeature && strNode.equals("param")) {
|
||||
paramType = xml.getAttributeValue(null, "name");
|
||||
if (paramType.equals("service")) // check if it is using the older service param
|
||||
service = xml.getAttributeValue(null, "value");
|
||||
else if (paramType.equals("package") || paramType.equals("android-package"))
|
||||
pluginClass = xml.getAttributeValue(null,"value");
|
||||
else if (paramType.equals("onload"))
|
||||
onload = "true".equals(xml.getAttributeValue(null, "value"));
|
||||
}
|
||||
else if (strNode.equals("preference")) {
|
||||
String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
|
||||
String value = xml.getAttributeValue(null, "value");
|
||||
prefs.set(name, value);
|
||||
}
|
||||
else if (strNode.equals("content")) {
|
||||
String src = xml.getAttributeValue(null, "src");
|
||||
if (src != null) {
|
||||
setStartUrl(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleEndTag(XmlPullParser xml) {
|
||||
String strNode = xml.getName();
|
||||
if (strNode.equals("feature")) {
|
||||
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
|
||||
|
||||
service = "";
|
||||
pluginClass = "";
|
||||
insideFeature = false;
|
||||
onload = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setStartUrl(String src) {
|
||||
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
|
||||
Matcher matcher = schemeRegex.matcher(src);
|
||||
if (matcher.find()) {
|
||||
launchUrl = src;
|
||||
} else {
|
||||
if (src.charAt(0) == '/') {
|
||||
src = src.substring(1);
|
||||
}
|
||||
launchUrl = "file:///android_asset/www/" + src;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,519 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* This class is the main Android activity that represents the Cordova
|
||||
* application. It should be extended by the user to load the specific
|
||||
* html file that contains the application.
|
||||
*
|
||||
* As an example:
|
||||
*
|
||||
* <pre>
|
||||
* package org.apache.cordova.examples;
|
||||
*
|
||||
* import android.os.Bundle;
|
||||
* import org.apache.cordova.*;
|
||||
*
|
||||
* public class Example extends CordovaActivity {
|
||||
* @Override
|
||||
* public void onCreate(Bundle savedInstanceState) {
|
||||
* super.onCreate(savedInstanceState);
|
||||
* super.init();
|
||||
* // Load your application
|
||||
* loadUrl(launchUrl);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Cordova xml configuration: Cordova uses a configuration file at
|
||||
* res/xml/config.xml to specify its settings. See "The config.xml File"
|
||||
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
|
||||
* for the configuration. The use of the set*Property() methods is
|
||||
* deprecated in favor of the config.xml file.
|
||||
*
|
||||
*/
|
||||
public class CordovaActivity extends Activity {
|
||||
public static String TAG = "CordovaActivity";
|
||||
|
||||
// The webview for our app
|
||||
protected CordovaWebView appView;
|
||||
|
||||
private static int ACTIVITY_STARTING = 0;
|
||||
private static int ACTIVITY_RUNNING = 1;
|
||||
private static int ACTIVITY_EXITING = 2;
|
||||
|
||||
// Keep app running when pause is received. (default = true)
|
||||
// If true, then the JavaScript and native code continue to run in the background
|
||||
// when another application (activity) is started.
|
||||
protected boolean keepRunning = true;
|
||||
|
||||
// Flag to keep immersive mode if set to fullscreen
|
||||
protected boolean immersiveMode;
|
||||
|
||||
// Read from config.xml:
|
||||
protected CordovaPreferences preferences;
|
||||
protected String launchUrl;
|
||||
protected ArrayList<PluginEntry> pluginEntries;
|
||||
protected CordovaInterfaceImpl cordovaInterface;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
|
||||
loadConfig();
|
||||
|
||||
String logLevel = preferences.getString("loglevel", "ERROR");
|
||||
LOG.setLogLevel(logLevel);
|
||||
|
||||
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
|
||||
LOG.d(TAG, "CordovaActivity.onCreate()");
|
||||
|
||||
if (!preferences.getBoolean("ShowTitle", false)) {
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
|
||||
if (preferences.getBoolean("SetFullscreen", false)) {
|
||||
LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
|
||||
preferences.set("Fullscreen", true);
|
||||
}
|
||||
if (preferences.getBoolean("Fullscreen", false)) {
|
||||
// NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
|
||||
// (as was the case in previous cordova versions)
|
||||
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
||||
immersiveMode = true;
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
} else {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
cordovaInterface = makeCordovaInterface();
|
||||
if (savedInstanceState != null) {
|
||||
cordovaInterface.restoreInstanceState(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
appView = makeWebView();
|
||||
createViews();
|
||||
if (!appView.isInitialized()) {
|
||||
appView.init(cordovaInterface, pluginEntries, preferences);
|
||||
}
|
||||
cordovaInterface.onCordovaInit(appView.getPluginManager());
|
||||
|
||||
// Wire the hardware volume controls to control media if desired.
|
||||
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
||||
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void loadConfig() {
|
||||
ConfigXmlParser parser = new ConfigXmlParser();
|
||||
parser.parse(this);
|
||||
preferences = parser.getPreferences();
|
||||
preferences.setPreferencesBundle(getIntent().getExtras());
|
||||
launchUrl = parser.getLaunchUrl();
|
||||
pluginEntries = parser.getPluginEntries();
|
||||
Config.parser = parser;
|
||||
}
|
||||
|
||||
//Suppressing warnings in AndroidStudio
|
||||
@SuppressWarnings({"deprecation", "ResourceType"})
|
||||
protected void createViews() {
|
||||
//Why are we setting a constant as the ID? This should be investigated
|
||||
appView.getView().setId(100);
|
||||
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
setContentView(appView.getView());
|
||||
|
||||
if (preferences.contains("BackgroundColor")) {
|
||||
try {
|
||||
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
||||
// Background of activity:
|
||||
appView.getView().setBackgroundColor(backgroundColor);
|
||||
}
|
||||
catch (NumberFormatException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
appView.getView().requestFocusFromTouch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the default web view object.
|
||||
* <p/>
|
||||
* Override this to customize the webview that is used.
|
||||
*/
|
||||
protected CordovaWebView makeWebView() {
|
||||
return new CordovaWebViewImpl(makeWebViewEngine());
|
||||
}
|
||||
|
||||
protected CordovaWebViewEngine makeWebViewEngine() {
|
||||
return CordovaWebViewImpl.createEngine(this, preferences);
|
||||
}
|
||||
|
||||
protected CordovaInterfaceImpl makeCordovaInterface() {
|
||||
return new CordovaInterfaceImpl(this) {
|
||||
@Override
|
||||
public Object onMessage(String id, Object data) {
|
||||
// Plumb this to CordovaActivity.onMessage for backwards compatibility
|
||||
return CordovaActivity.this.onMessage(id, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
public void loadUrl(String url) {
|
||||
if (appView == null) {
|
||||
init();
|
||||
}
|
||||
|
||||
// If keepRunning
|
||||
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
||||
|
||||
appView.loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity.
|
||||
*/
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LOG.d(TAG, "Paused the activity.");
|
||||
|
||||
if (this.appView != null) {
|
||||
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
|
||||
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
|
||||
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
|
||||
this.appView.handlePause(keepRunning);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity receives a new intent
|
||||
*/
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
//Forward to plugins
|
||||
if (this.appView != null)
|
||||
this.appView.onNewIntent(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LOG.d(TAG, "Resumed the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
// Force window to have focus, so application always
|
||||
// receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
|
||||
this.getWindow().getDecorView().requestFocus();
|
||||
|
||||
this.appView.handleResume(this.keepRunning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
LOG.d(TAG, "Stopped the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
this.appView.handleStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
LOG.d(TAG, "Started the activity.");
|
||||
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
this.appView.handleStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LOG.d(TAG, "CordovaActivity.onDestroy()");
|
||||
super.onDestroy();
|
||||
|
||||
if (this.appView != null) {
|
||||
appView.handleDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when view focus is changed
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus && immersiveMode) {
|
||||
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
|
||||
cordovaInterface.setActivityResultRequestCode(requestCode);
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
||||
* the resultCode it returned, and any additional data from it.
|
||||
*
|
||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||
* allowing you to identify who this result came from.
|
||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
|
||||
super.onActivityResult(requestCode, resultCode, intent);
|
||||
cordovaInterface.onActivityResult(requestCode, resultCode, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
||||
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
||||
*
|
||||
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||
* @param description A String describing the error.
|
||||
* @param failingUrl The url that failed to load.
|
||||
*/
|
||||
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
|
||||
final CordovaActivity me = this;
|
||||
|
||||
// If errorUrl specified, then load it
|
||||
final String errorUrl = preferences.getString("errorUrl", null);
|
||||
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
|
||||
// Load URL on UI thread
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
me.appView.showWebPage(errorUrl, false, true, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
// If not, then display error dialog
|
||||
else {
|
||||
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (exit) {
|
||||
me.appView.getView().setVisibility(View.GONE);
|
||||
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error dialog and optionally exit application.
|
||||
*/
|
||||
public void displayError(final String title, final String message, final String button, final boolean exit) {
|
||||
final CordovaActivity me = this;
|
||||
me.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(me);
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle(title);
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(button,
|
||||
new AlertDialog.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
if (exit) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
dlg.create();
|
||||
dlg.show();
|
||||
} catch (Exception e) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Hook in Cordova for menu plugins
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (appView != null) {
|
||||
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object or null
|
||||
*/
|
||||
public Object onMessage(String id, Object data) {
|
||||
if ("onReceivedError".equals(id)) {
|
||||
JSONObject d = (JSONObject) data;
|
||||
try {
|
||||
this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if ("exit".equals(id)) {
|
||||
finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
cordovaInterface.onSaveInstanceState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (this.appView == null) {
|
||||
return;
|
||||
}
|
||||
PluginManager pm = this.appView.getPluginManager();
|
||||
if (pm != null) {
|
||||
pm.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the user grants permissions
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String permissions[],
|
||||
int[] grantResults) {
|
||||
try
|
||||
{
|
||||
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
catch (JSONException e)
|
||||
{
|
||||
LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
public class CordovaArgs {
|
||||
private JSONArray baseArgs;
|
||||
|
||||
public CordovaArgs(JSONArray args) {
|
||||
this.baseArgs = args;
|
||||
}
|
||||
|
||||
|
||||
// Pass through the basics to the base args.
|
||||
public Object get(int index) throws JSONException {
|
||||
return baseArgs.get(index);
|
||||
}
|
||||
|
||||
public boolean getBoolean(int index) throws JSONException {
|
||||
return baseArgs.getBoolean(index);
|
||||
}
|
||||
|
||||
public double getDouble(int index) throws JSONException {
|
||||
return baseArgs.getDouble(index);
|
||||
}
|
||||
|
||||
public int getInt(int index) throws JSONException {
|
||||
return baseArgs.getInt(index);
|
||||
}
|
||||
|
||||
public JSONArray getJSONArray(int index) throws JSONException {
|
||||
return baseArgs.getJSONArray(index);
|
||||
}
|
||||
|
||||
public JSONObject getJSONObject(int index) throws JSONException {
|
||||
return baseArgs.getJSONObject(index);
|
||||
}
|
||||
|
||||
public long getLong(int index) throws JSONException {
|
||||
return baseArgs.getLong(index);
|
||||
}
|
||||
|
||||
public String getString(int index) throws JSONException {
|
||||
return baseArgs.getString(index);
|
||||
}
|
||||
|
||||
|
||||
public Object opt(int index) {
|
||||
return baseArgs.opt(index);
|
||||
}
|
||||
|
||||
public boolean optBoolean(int index) {
|
||||
return baseArgs.optBoolean(index);
|
||||
}
|
||||
|
||||
public double optDouble(int index) {
|
||||
return baseArgs.optDouble(index);
|
||||
}
|
||||
|
||||
public int optInt(int index) {
|
||||
return baseArgs.optInt(index);
|
||||
}
|
||||
|
||||
public JSONArray optJSONArray(int index) {
|
||||
return baseArgs.optJSONArray(index);
|
||||
}
|
||||
|
||||
public JSONObject optJSONObject(int index) {
|
||||
return baseArgs.optJSONObject(index);
|
||||
}
|
||||
|
||||
public long optLong(int index) {
|
||||
return baseArgs.optLong(index);
|
||||
}
|
||||
|
||||
public String optString(int index) {
|
||||
return baseArgs.optString(index);
|
||||
}
|
||||
|
||||
public boolean isNull(int index) {
|
||||
return baseArgs.isNull(index);
|
||||
}
|
||||
|
||||
|
||||
// The interesting custom helpers.
|
||||
public byte[] getArrayBuffer(int index) throws JSONException {
|
||||
String encoded = baseArgs.getString(index);
|
||||
return Base64.decode(encoded, Base64.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
public class CordovaBridge {
|
||||
private static final String LOG_TAG = "CordovaBridge";
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||
|
||||
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
}
|
||||
|
||||
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
if (!verifySecret("exec()", bridgeSecret)) {
|
||||
return null;
|
||||
}
|
||||
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
|
||||
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
|
||||
if (arguments == null) {
|
||||
return "@Null arguments.";
|
||||
}
|
||||
|
||||
jsMessageQueue.setPaused(true);
|
||||
try {
|
||||
// Tell the resourceApi what thread the JS is running on.
|
||||
CordovaResourceApi.jsThread = Thread.currentThread();
|
||||
|
||||
pluginManager.exec(service, action, callbackId, arguments);
|
||||
String ret = null;
|
||||
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
|
||||
ret = jsMessageQueue.popAndEncode(false);
|
||||
}
|
||||
return ret;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
} finally {
|
||||
jsMessageQueue.setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
|
||||
return;
|
||||
}
|
||||
jsMessageQueue.setBridgeMode(value);
|
||||
}
|
||||
|
||||
public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
|
||||
return null;
|
||||
}
|
||||
return jsMessageQueue.popAndEncode(fromOnlineEvent);
|
||||
}
|
||||
|
||||
private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
|
||||
if (!jsMessageQueue.isBridgeEnabled()) {
|
||||
if (bridgeSecret == -1) {
|
||||
LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
|
||||
} else {
|
||||
LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Bridge secret wrong and bridge not due to it being from the previous page.
|
||||
if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
|
||||
LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
|
||||
clearBridgeSecret();
|
||||
throw new IllegalAccessException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Called on page transitions */
|
||||
void clearBridgeSecret() {
|
||||
expectedBridgeSecret = -1;
|
||||
}
|
||||
|
||||
public boolean isSecretEstablished() {
|
||||
return expectedBridgeSecret != -1;
|
||||
}
|
||||
|
||||
/** Called by cordova.js to initialize the bridge. */
|
||||
//On old Androids SecureRandom isn't really secure, this is the least of your problems if
|
||||
//you're running Android 4.3 and below in 2017
|
||||
@SuppressLint("TrulyRandom")
|
||||
int generateBridgeSecret() {
|
||||
SecureRandom randGen = new SecureRandom();
|
||||
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
|
||||
return expectedBridgeSecret;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
jsMessageQueue.reset();
|
||||
clearBridgeSecret();
|
||||
}
|
||||
|
||||
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
|
||||
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
|
||||
JSONArray array;
|
||||
try {
|
||||
array = new JSONArray(defaultValue.substring(4));
|
||||
int bridgeSecret = array.getInt(0);
|
||||
String service = array.getString(1);
|
||||
String action = array.getString(2);
|
||||
String callbackId = array.getString(3);
|
||||
String r = jsExec(bridgeSecret, service, action, callbackId, message);
|
||||
return r == null ? "" : r;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Sets the native->JS bridge mode.
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
|
||||
try {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
|
||||
jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
|
||||
} catch (NumberFormatException e){
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Polling for JavaScript messages
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
|
||||
try {
|
||||
String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
|
||||
return r == null ? "" : r;
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||
// Protect against random iframes being able to talk through the bridge.
|
||||
// Trust only pages which the app would have been allowed to navigate to anyway.
|
||||
if (pluginManager.shouldAllowBridgeAccess(origin)) {
|
||||
// Enable the bridge
|
||||
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||
jsMessageQueue.setBridgeMode(bridgeMode);
|
||||
// Tell JS the bridge secret.
|
||||
int secret = generateBridgeSecret();
|
||||
return ""+secret;
|
||||
} else {
|
||||
LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.webkit.ClientCertRequest;
|
||||
|
||||
/**
|
||||
* Implementation of the ICordovaClientCertRequest for Android WebView.
|
||||
*
|
||||
*/
|
||||
public class CordovaClientCertRequest implements ICordovaClientCertRequest {
|
||||
|
||||
private final ClientCertRequest request;
|
||||
|
||||
public CordovaClientCertRequest(ClientCertRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel this request
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void cancel()
|
||||
{
|
||||
request.cancel();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the host name of the server requesting the certificate.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public String getHost()
|
||||
{
|
||||
return request.getHost();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the acceptable types of asymmetric keys (can be null).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public String[] getKeyTypes()
|
||||
{
|
||||
return request.getKeyTypes();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the port number of the server requesting the certificate.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public int getPort()
|
||||
{
|
||||
return request.getPort();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public Principal[] getPrincipals()
|
||||
{
|
||||
return request.getPrincipals();
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore the request for now. Do not remember user's choice.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void ignore()
|
||||
{
|
||||
request.ignore();
|
||||
}
|
||||
|
||||
/*
|
||||
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||
*
|
||||
* @param privateKey The privateKey
|
||||
* @param chain The certificate chain
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
|
||||
{
|
||||
request.proceed(privateKey, chain);
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.EditText;
|
||||
|
||||
/**
|
||||
* Helper class for WebViews to implement prompt(), alert(), confirm() dialogs.
|
||||
*/
|
||||
public class CordovaDialogsHelper {
|
||||
private final Context context;
|
||||
private AlertDialog lastHandledDialog;
|
||||
|
||||
public CordovaDialogsHelper(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void showAlert(String message, final Result result) {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle("Alert");
|
||||
//Don't let alerts break the back button
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new AlertDialog.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(true, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
result.gotResult(true, null);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
}
|
||||
|
||||
public void showConfirm(String message, final Result result) {
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||
dlg.setMessage(message);
|
||||
dlg.setTitle("Confirm");
|
||||
dlg.setCancelable(true);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(true, null);
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
//DO NOTHING
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
result.gotResult(false, null);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a prompt dialog to the user.
|
||||
* If the client returns true, WebView will assume that the client will
|
||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
*/
|
||||
public void showPrompt(String message, String defaultValue, final Result result) {
|
||||
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(context);
|
||||
dlg.setMessage(message);
|
||||
final EditText input = new EditText(context);
|
||||
if (defaultValue != null) {
|
||||
input.setText(defaultValue);
|
||||
}
|
||||
dlg.setView(input);
|
||||
dlg.setCancelable(false);
|
||||
dlg.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String userText = input.getText().toString();
|
||||
result.gotResult(true, userText);
|
||||
}
|
||||
});
|
||||
dlg.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.gotResult(false, null);
|
||||
}
|
||||
});
|
||||
lastHandledDialog = dlg.show();
|
||||
}
|
||||
|
||||
public void destroyLastDialog(){
|
||||
if (lastHandledDialog != null){
|
||||
lastHandledDialog.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Result {
|
||||
public void gotResult(boolean success, String value);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.webkit.HttpAuthHandler;
|
||||
|
||||
/**
|
||||
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
|
||||
* specifying user credentials.
|
||||
*/
|
||||
public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
|
||||
|
||||
private final HttpAuthHandler handler;
|
||||
|
||||
public CordovaHttpAuthHandler(HttpAuthHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the WebView to cancel the authentication request.
|
||||
*/
|
||||
public void cancel () {
|
||||
this.handler.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the WebView to proceed with the authentication with the given credentials.
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
*/
|
||||
public void proceed (String username, String password) {
|
||||
this.handler.proceed(username, password);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* The Activity interface that is implemented by CordovaActivity.
|
||||
* It is used to isolate plugin development, and remove dependency on entire Cordova library.
|
||||
*/
|
||||
public interface CordovaInterface {
|
||||
|
||||
/**
|
||||
* Launch an activity for which you would like a result when it finished. When this activity exits,
|
||||
* your onActivityResult() method will be called.
|
||||
*
|
||||
* @param command The command object
|
||||
* @param intent The intent to start
|
||||
* @param requestCode The request code that is passed to callback to identify the activity
|
||||
*/
|
||||
abstract public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode);
|
||||
|
||||
/**
|
||||
* Set the plugin to be called when a sub-activity exits.
|
||||
*
|
||||
* @param plugin The plugin on which onActivityResult is to be called
|
||||
*/
|
||||
abstract public void setActivityResultCallback(CordovaPlugin plugin);
|
||||
|
||||
/**
|
||||
* Get the Android activity.
|
||||
*
|
||||
* If a custom engine lives outside of the Activity's lifecycle the return value may be null.
|
||||
*
|
||||
* @return the Activity
|
||||
*/
|
||||
public abstract Activity getActivity();
|
||||
|
||||
/**
|
||||
* Get the Android context.
|
||||
*
|
||||
* @return the Context
|
||||
*/
|
||||
public Context getContext();
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object or null
|
||||
*/
|
||||
public Object onMessage(String id, Object data);
|
||||
|
||||
/**
|
||||
* Returns a shared thread pool that can be used for background tasks.
|
||||
*/
|
||||
public ExecutorService getThreadPool();
|
||||
|
||||
/**
|
||||
* Sends a permission request to the activity for one permission.
|
||||
*/
|
||||
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
|
||||
|
||||
/**
|
||||
* Sends a permission request to the activity for a group of permissions
|
||||
*/
|
||||
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
|
||||
|
||||
/**
|
||||
* Check for a permission. Returns true if the permission is granted, false otherwise.
|
||||
*/
|
||||
public boolean hasPermission(String permission);
|
||||
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Default implementation of CordovaInterface.
|
||||
*/
|
||||
public class CordovaInterfaceImpl implements CordovaInterface {
|
||||
private static final String TAG = "CordovaInterfaceImpl";
|
||||
protected Activity activity;
|
||||
protected ExecutorService threadPool;
|
||||
protected PluginManager pluginManager;
|
||||
|
||||
protected ActivityResultHolder savedResult;
|
||||
protected CallbackMap permissionResultCallbacks;
|
||||
protected CordovaPlugin activityResultCallback;
|
||||
protected String initCallbackService;
|
||||
protected int activityResultRequestCode;
|
||||
protected boolean activityWasDestroyed = false;
|
||||
protected Bundle savedPluginState;
|
||||
|
||||
public CordovaInterfaceImpl(Activity activity) {
|
||||
this(activity, Executors.newCachedThreadPool());
|
||||
}
|
||||
|
||||
public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
|
||||
this.activity = activity;
|
||||
this.threadPool = threadPool;
|
||||
this.permissionResultCallbacks = new CallbackMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
|
||||
setActivityResultCallback(command);
|
||||
try {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
} catch (RuntimeException e) { // E.g.: ActivityNotFoundException
|
||||
activityResultCallback = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActivityResultCallback(CordovaPlugin plugin) {
|
||||
// Cancel any previously pending activity.
|
||||
if (activityResultCallback != null) {
|
||||
activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
|
||||
}
|
||||
activityResultCallback = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onMessage(String id, Object data) {
|
||||
if ("exit".equals(id)) {
|
||||
activity.finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getThreadPool() {
|
||||
return threadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches any pending onActivityResult callbacks and sends the resume event if the
|
||||
* Activity was destroyed by the OS.
|
||||
*/
|
||||
public void onCordovaInit(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
if (savedResult != null) {
|
||||
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
|
||||
} else if(activityWasDestroyed) {
|
||||
// If there was no Activity result, we still need to send out the resume event if the
|
||||
// Activity was destroyed by the OS
|
||||
activityWasDestroyed = false;
|
||||
if(pluginManager != null)
|
||||
{
|
||||
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
if(appPlugin != null) {
|
||||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("action", "resume");
|
||||
} catch (JSONException e) {
|
||||
LOG.e(TAG, "Failed to create event message", e);
|
||||
}
|
||||
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, obj));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
|
||||
*/
|
||||
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
CordovaPlugin callback = activityResultCallback;
|
||||
if(callback == null && initCallbackService != null) {
|
||||
// The application was restarted, but had defined an initial callback
|
||||
// before being shut down.
|
||||
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
|
||||
if (pluginManager != null) {
|
||||
callback = pluginManager.getPlugin(initCallbackService);
|
||||
if(callback != null) {
|
||||
callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
|
||||
new ResumeCallback(callback.getServiceName(), pluginManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
activityResultCallback = null;
|
||||
|
||||
if (callback != null) {
|
||||
LOG.d(TAG, "Sending activity result to plugin");
|
||||
initCallbackService = null;
|
||||
savedResult = null;
|
||||
callback.onActivityResult(requestCode, resultCode, intent);
|
||||
return true;
|
||||
}
|
||||
LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!" : "."));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from your startActivityForResult() overload. This is required to catch the case
|
||||
* where plugins use Activity.startActivityForResult() + CordovaInterface.setActivityResultCallback()
|
||||
* rather than CordovaInterface.startActivityForResult().
|
||||
*/
|
||||
public void setActivityResultRequestCode(int requestCode) {
|
||||
activityResultRequestCode = requestCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves parameters for startActivityForResult().
|
||||
*/
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
if (activityResultCallback != null) {
|
||||
String serviceName = activityResultCallback.getServiceName();
|
||||
outState.putString("callbackService", serviceName);
|
||||
}
|
||||
if(pluginManager != null){
|
||||
outState.putBundle("plugin", pluginManager.onSaveInstanceState());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
|
||||
*/
|
||||
public void restoreInstanceState(Bundle savedInstanceState) {
|
||||
initCallbackService = savedInstanceState.getString("callbackService");
|
||||
savedPluginState = savedInstanceState.getBundle("plugin");
|
||||
activityWasDestroyed = true;
|
||||
}
|
||||
|
||||
private static class ActivityResultHolder {
|
||||
private int requestCode;
|
||||
private int resultCode;
|
||||
private Intent intent;
|
||||
|
||||
public ActivityResultHolder(int requestCode, int resultCode, Intent intent) {
|
||||
this.requestCode = requestCode;
|
||||
this.resultCode = resultCode;
|
||||
this.intent = intent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the user grants permissions
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
public void onRequestPermissionResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) throws JSONException {
|
||||
Pair<CordovaPlugin, Integer> callback = permissionResultCallbacks.getAndRemoveCallback(requestCode);
|
||||
if(callback != null) {
|
||||
callback.first.onRequestPermissionResult(callback.second, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||
String[] permissions = new String [1];
|
||||
permissions[0] = permission;
|
||||
requestPermissions(plugin, requestCode, permissions);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
|
||||
int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
|
||||
getActivity().requestPermissions(permissions, mappedRequestCode);
|
||||
}
|
||||
|
||||
public boolean hasPermission(String permission)
|
||||
{
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
{
|
||||
int result = activity.checkSelfPermission(permission);
|
||||
return PackageManager.PERMISSION_GRANTED == result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,422 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.CordovaArgs;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Plugins must extend this class and override one of the execute methods.
|
||||
*/
|
||||
public class CordovaPlugin {
|
||||
public CordovaWebView webView;
|
||||
public CordovaInterface cordova;
|
||||
protected CordovaPreferences preferences;
|
||||
private String serviceName;
|
||||
|
||||
/**
|
||||
* Call this after constructing to initialize the plugin.
|
||||
* Final because we want to be able to change args without breaking plugins.
|
||||
*/
|
||||
public final void privateInitialize(String serviceName, CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
|
||||
assert this.cordova == null;
|
||||
this.serviceName = serviceName;
|
||||
this.cordova = cordova;
|
||||
this.webView = webView;
|
||||
this.preferences = preferences;
|
||||
initialize(cordova, webView);
|
||||
pluginInitialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after plugin construction and fields have been initialized.
|
||||
* Prefer to use pluginInitialize instead since there is no value in
|
||||
* having parameters on the initialize() function.
|
||||
*/
|
||||
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after plugin construction and fields have been initialized.
|
||||
*/
|
||||
protected void pluginInitialize() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin's service name (what you'd use when calling pluginManger.getPlugin())
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||
* cordova.getThreadPool().execute(runnable);
|
||||
*
|
||||
* To run on the UI thread, use:
|
||||
* cordova.getActivity().runOnUiThread(runnable);
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param rawArgs The exec() arguments in JSON form.
|
||||
* @param callbackContext The callback context used when calling back into JavaScript.
|
||||
* @return Whether the action was valid.
|
||||
*/
|
||||
public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
|
||||
JSONArray args = new JSONArray(rawArgs);
|
||||
return execute(action, args, callbackContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||
* cordova.getThreadPool().execute(runnable);
|
||||
*
|
||||
* To run on the UI thread, use:
|
||||
* cordova.getActivity().runOnUiThread(runnable);
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args The exec() arguments.
|
||||
* @param callbackContext The callback context used when calling back into JavaScript.
|
||||
* @return Whether the action was valid.
|
||||
*/
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
CordovaArgs cordovaArgs = new CordovaArgs(args);
|
||||
return execute(action, cordovaArgs, callbackContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request.
|
||||
*
|
||||
* This method is called from the WebView thread. To do a non-trivial amount of work, use:
|
||||
* cordova.getThreadPool().execute(runnable);
|
||||
*
|
||||
* To run on the UI thread, use:
|
||||
* cordova.getActivity().runOnUiThread(runnable);
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args The exec() arguments, wrapped with some Cordova helpers.
|
||||
* @param callbackContext The callback context used when calling back into JavaScript.
|
||||
* @return Whether the action was valid.
|
||||
*/
|
||||
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity.
|
||||
*
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onPause(boolean multitasking) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user.
|
||||
*
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onResume(boolean multitasking) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
public void onStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
public void onStop() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity receives a new intent.
|
||||
*/
|
||||
public void onNewIntent(Intent intent) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Activity is being destroyed (e.g. if a plugin calls out to an external
|
||||
* Activity and the OS kills the CordovaActivity in the background). The plugin should save its
|
||||
* state in this method only if it is awaiting the result of an external Activity and needs
|
||||
* to preserve some information so as to handle that result; onRestoreStateForActivityResult()
|
||||
* will only be called if the plugin is the recipient of an Activity result
|
||||
*
|
||||
* @return Bundle containing the state of the plugin or null if state does not need to be saved
|
||||
*/
|
||||
public Bundle onSaveInstanceState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a plugin is the recipient of an Activity result after the CordovaActivity has
|
||||
* been destroyed. The Bundle will be the same as the one the plugin returned in
|
||||
* onSaveInstanceState()
|
||||
*
|
||||
* @param state Bundle containing the state of the plugin
|
||||
* @param callbackContext Replacement Context to return the plugin result to
|
||||
*/
|
||||
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}
|
||||
|
||||
/**
|
||||
* Called when a message is sent to plugin.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object to stop propagation or null
|
||||
*/
|
||||
public Object onMessage(String id, Object data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
||||
* the resultCode it returned, and any additional data from it.
|
||||
*
|
||||
* @param requestCode The request code originally supplied to startActivityForResult(),
|
||||
* allowing you to identify who this result came from.
|
||||
* @param resultCode The integer result code returned by the child activity through its setResult().
|
||||
* @param intent An Intent, which can return result data to the caller (various data can be
|
||||
* attached to Intent "extras").
|
||||
*/
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for blocking the loading of external resources.
|
||||
*
|
||||
* This will be called when the WebView's shouldInterceptRequest wants to
|
||||
* know whether to open a connection to an external resource. Return false
|
||||
* to block the request: if any plugin returns false, Cordova will block
|
||||
* the request. If all plugins return null, the default policy will be
|
||||
* enforced. If at least one plugin returns true, and no plugins return
|
||||
* false, then the request will proceed.
|
||||
*
|
||||
* Note that this only affects resource requests which are routed through
|
||||
* WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
|
||||
* img tag loads. WebSockets and media requests (such as <video> and <audio>
|
||||
* tags) are not affected by this method. Use CSP headers to control access
|
||||
* to such resources.
|
||||
*/
|
||||
public Boolean shouldAllowRequest(String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for blocking navigation by the Cordova WebView. This applies both to top-level and
|
||||
* iframe navigations.
|
||||
*
|
||||
* This will be called when the WebView's needs to know whether to navigate
|
||||
* to a new page. Return false to block the navigation: if any plugin
|
||||
* returns false, Cordova will block the navigation. If all plugins return
|
||||
* null, the default policy will be enforced. It at least one plugin returns
|
||||
* true, and no plugins return false, then the navigation will proceed.
|
||||
*/
|
||||
public Boolean shouldAllowNavigation(String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for allowing page to call exec(). By default, this returns the result of
|
||||
* shouldAllowNavigation(). It's generally unsafe to allow untrusted content to be loaded
|
||||
* into a CordovaWebView, even within an iframe, so it's best not to touch this.
|
||||
*/
|
||||
public Boolean shouldAllowBridgeAccess(String url) {
|
||||
return shouldAllowNavigation(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for blocking the launching of Intents by the Cordova application.
|
||||
*
|
||||
* This will be called when the WebView will not navigate to a page, but
|
||||
* could launch an intent to handle the URL. Return false to block this: if
|
||||
* any plugin returns false, Cordova will block the navigation. If all
|
||||
* plugins return null, the default policy will be enforced. If at least one
|
||||
* plugin returns true, and no plugins return false, then the URL will be
|
||||
* opened.
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows plugins to handle a link being clicked. Return true here to cancel the navigation.
|
||||
*
|
||||
* @param url The URL that is trying to be loaded in the Cordova webview.
|
||||
* @return Return true to prevent the URL from loading. Default is false.
|
||||
*/
|
||||
public boolean onOverrideUrlLoading(String url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
|
||||
* To handle the request directly, return a URI in the form:
|
||||
*
|
||||
* cdvplugin://pluginId/...
|
||||
*
|
||||
* And implement handleOpenForRead().
|
||||
* To make this easier, use the toPluginUri() and fromPluginUri() helpers:
|
||||
*
|
||||
* public Uri remapUri(Uri uri) { return toPluginUri(uri); }
|
||||
*
|
||||
* public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||
* Uri origUri = fromPluginUri(uri);
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
public Uri remapUri(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to handle CordovaResourceApi.openForRead() calls for a cdvplugin://pluginId/ URL.
|
||||
* Should never return null.
|
||||
* Added in cordova-android@4.0.0
|
||||
*/
|
||||
public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
|
||||
throw new FileNotFoundException("Plugin can't handle uri: " + uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refer to remapUri()
|
||||
* Added in cordova-android@4.0.0
|
||||
*/
|
||||
protected Uri toPluginUri(Uri origUri) {
|
||||
return new Uri.Builder()
|
||||
.scheme(CordovaResourceApi.PLUGIN_URI_SCHEME)
|
||||
.authority(serviceName)
|
||||
.appendQueryParameter("origUri", origUri.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refer to remapUri()
|
||||
* Added in cordova-android@4.0.0
|
||||
*/
|
||||
protected Uri fromPluginUri(Uri pluginUri) {
|
||||
return Uri.parse(pluginUri.getQueryParameter("origUri"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the WebView does a top-level navigation or refreshes.
|
||||
*
|
||||
* Plugins should stop any long-running processes and clean up internal state.
|
||||
*
|
||||
* Does nothing by default.
|
||||
*/
|
||||
public void onReset() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system received an HTTP authentication request. Plugin can use
|
||||
* the supplied HttpAuthHandler to process this auth challenge.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||
* @param host The host requiring authentication
|
||||
* @param realm The realm for which authentication is required
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when he system received an SSL client certificate request. Plugin can use
|
||||
* the supplied ClientCertRequest to process this certificate challenge.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param request The client certificate request
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the Plugin Manager when we need to actually request permissions
|
||||
*
|
||||
* @param requestCode Passed to the activity to track the request
|
||||
*
|
||||
* @return Returns the permission that was stored in the plugin
|
||||
*/
|
||||
|
||||
public void requestPermissions(int requestCode) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by the WebView implementation to check for geolocation permissions, can be used
|
||||
* by other Java methods in the event that a plugin is using this as a dependency.
|
||||
*
|
||||
* @return Returns true if the plugin has all the permissions it needs to operate.
|
||||
*/
|
||||
|
||||
public boolean hasPermisssion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the user grants permissions
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
public void onRequestPermissionResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) throws JSONException {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class CordovaPreferences {
|
||||
private HashMap<String, String> prefs = new HashMap<String, String>(20);
|
||||
private Bundle preferencesBundleExtras;
|
||||
|
||||
public void setPreferencesBundle(Bundle extras) {
|
||||
preferencesBundleExtras = extras;
|
||||
}
|
||||
|
||||
public void set(String name, String value) {
|
||||
prefs.put(name.toLowerCase(Locale.ENGLISH), value);
|
||||
}
|
||||
|
||||
public void set(String name, boolean value) {
|
||||
set(name, "" + value);
|
||||
}
|
||||
|
||||
public void set(String name, int value) {
|
||||
set(name, "" + value);
|
||||
}
|
||||
|
||||
public void set(String name, double value) {
|
||||
set(name, "" + value);
|
||||
}
|
||||
|
||||
public Map<String, String> getAll() {
|
||||
return prefs;
|
||||
}
|
||||
|
||||
public boolean getBoolean(String name, boolean defaultValue) {
|
||||
name = name.toLowerCase(Locale.ENGLISH);
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Added in 4.0.0
|
||||
public boolean contains(String name) {
|
||||
return getString(name, null) != null;
|
||||
}
|
||||
|
||||
public int getInteger(String name, int defaultValue) {
|
||||
name = name.toLowerCase(Locale.ENGLISH);
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
// Use Integer.decode() can't handle it if the highest bit is set.
|
||||
return (int)(long)Long.decode(value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public double getDouble(String name, double defaultValue) {
|
||||
name = name.toLowerCase(Locale.ENGLISH);
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
return Double.valueOf(value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public String getString(String name, String defaultValue) {
|
||||
name = name.toLowerCase(Locale.ENGLISH);
|
||||
String value = prefs.get(name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,472 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.util.Base64;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* What this class provides:
|
||||
* 1. Helpers for reading & writing to URLs.
|
||||
* - E.g. handles assets, resources, content providers, files, data URIs, http[s]
|
||||
* - E.g. Can be used to query for mime-type & content length.
|
||||
*
|
||||
* 2. To allow plugins to redirect URLs (via remapUrl).
|
||||
* - All plugins should call remapUrl() on URLs they receive from JS *before*
|
||||
* passing the URL onto other utility functions in this class.
|
||||
* - For an example usage of this, refer to the org.apache.cordova.file plugin.
|
||||
*
|
||||
* Future Work:
|
||||
* - Consider using a Cursor to query content URLs for their size (like the file plugin does).
|
||||
* - Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi
|
||||
* would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
|
||||
* - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient
|
||||
* for large payloads.
|
||||
*/
|
||||
public class CordovaResourceApi {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String LOG_TAG = "CordovaResourceApi";
|
||||
|
||||
public static final int URI_TYPE_FILE = 0;
|
||||
public static final int URI_TYPE_ASSET = 1;
|
||||
public static final int URI_TYPE_CONTENT = 2;
|
||||
public static final int URI_TYPE_RESOURCE = 3;
|
||||
public static final int URI_TYPE_DATA = 4;
|
||||
public static final int URI_TYPE_HTTP = 5;
|
||||
public static final int URI_TYPE_HTTPS = 6;
|
||||
public static final int URI_TYPE_PLUGIN = 7;
|
||||
public static final int URI_TYPE_UNKNOWN = -1;
|
||||
|
||||
public static final String PLUGIN_URI_SCHEME = "cdvplugin";
|
||||
|
||||
private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
|
||||
|
||||
public static Thread jsThread;
|
||||
|
||||
private final AssetManager assetManager;
|
||||
private final ContentResolver contentResolver;
|
||||
private final PluginManager pluginManager;
|
||||
private boolean threadCheckingEnabled = true;
|
||||
|
||||
|
||||
public CordovaResourceApi(Context context, PluginManager pluginManager) {
|
||||
this.contentResolver = context.getContentResolver();
|
||||
this.assetManager = context.getAssets();
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
public void setThreadCheckingEnabled(boolean value) {
|
||||
threadCheckingEnabled = value;
|
||||
}
|
||||
|
||||
public boolean isThreadCheckingEnabled() {
|
||||
return threadCheckingEnabled;
|
||||
}
|
||||
|
||||
|
||||
public static int getUriType(Uri uri) {
|
||||
assertNonRelative(uri);
|
||||
String scheme = uri.getScheme();
|
||||
if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_CONTENT;
|
||||
}
|
||||
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_RESOURCE;
|
||||
}
|
||||
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
|
||||
if (uri.getPath().startsWith("/android_asset/")) {
|
||||
return URI_TYPE_ASSET;
|
||||
}
|
||||
return URI_TYPE_FILE;
|
||||
}
|
||||
if ("data".equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_DATA;
|
||||
}
|
||||
if ("http".equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_HTTP;
|
||||
}
|
||||
if ("https".equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_HTTPS;
|
||||
}
|
||||
if (PLUGIN_URI_SCHEME.equalsIgnoreCase(scheme)) {
|
||||
return URI_TYPE_PLUGIN;
|
||||
}
|
||||
return URI_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
public Uri remapUri(Uri uri) {
|
||||
assertNonRelative(uri);
|
||||
Uri pluginUri = pluginManager.remapUri(uri);
|
||||
return pluginUri != null ? pluginUri : uri;
|
||||
}
|
||||
|
||||
public String remapPath(String path) {
|
||||
return remapUri(Uri.fromFile(new File(path))).getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File that points to the resource, or null if the resource
|
||||
* is not on the local filesystem.
|
||||
*/
|
||||
public File mapUriToFile(Uri uri) {
|
||||
assertBackgroundThread();
|
||||
switch (getUriType(uri)) {
|
||||
case URI_TYPE_FILE:
|
||||
return new File(uri.getPath());
|
||||
case URI_TYPE_CONTENT: {
|
||||
Cursor cursor = contentResolver.query(uri, LOCAL_FILE_PROJECTION, null, null, null);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
|
||||
if (columnIndex != -1 && cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
String realPath = cursor.getString(columnIndex);
|
||||
if (realPath != null) {
|
||||
return new File(realPath);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getMimeType(Uri uri) {
|
||||
switch (getUriType(uri)) {
|
||||
case URI_TYPE_FILE:
|
||||
case URI_TYPE_ASSET:
|
||||
return getMimeTypeFromPath(uri.getPath());
|
||||
case URI_TYPE_CONTENT:
|
||||
case URI_TYPE_RESOURCE:
|
||||
return contentResolver.getType(uri);
|
||||
case URI_TYPE_DATA: {
|
||||
return getDataUriMimeType(uri);
|
||||
}
|
||||
case URI_TYPE_HTTP:
|
||||
case URI_TYPE_HTTPS: {
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||
conn.setDoInput(false);
|
||||
conn.setRequestMethod("HEAD");
|
||||
String mimeType = conn.getHeaderField("Content-Type");
|
||||
if (mimeType != null) {
|
||||
mimeType = mimeType.split(";")[0];
|
||||
}
|
||||
return mimeType;
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//This already exists
|
||||
private String getMimeTypeFromPath(String path) {
|
||||
String extension = path;
|
||||
int lastDot = extension.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
extension = extension.substring(lastDot + 1);
|
||||
}
|
||||
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
|
||||
extension = extension.toLowerCase(Locale.getDefault());
|
||||
if (extension.equals("3ga")) {
|
||||
return "audio/3gpp";
|
||||
} else if (extension.equals("js")) {
|
||||
// Missing from the map :(.
|
||||
return "text/javascript";
|
||||
}
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a stream to the given URI, also providing the MIME type & length.
|
||||
* @return Never returns null.
|
||||
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||
* resolved before being passed into this function.
|
||||
* @throws Throws an IOException if the URI cannot be opened.
|
||||
* @throws Throws an IllegalStateException if called on a foreground thread.
|
||||
*/
|
||||
public OpenForReadResult openForRead(Uri uri) throws IOException {
|
||||
return openForRead(uri, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a stream to the given URI, also providing the MIME type & length.
|
||||
* @return Never returns null.
|
||||
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||
* resolved before being passed into this function.
|
||||
* @throws Throws an IOException if the URI cannot be opened.
|
||||
* @throws Throws an IllegalStateException if called on a foreground thread and skipThreadCheck is false.
|
||||
*/
|
||||
public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException {
|
||||
if (!skipThreadCheck) {
|
||||
assertBackgroundThread();
|
||||
}
|
||||
switch (getUriType(uri)) {
|
||||
case URI_TYPE_FILE: {
|
||||
FileInputStream inputStream = new FileInputStream(uri.getPath());
|
||||
String mimeType = getMimeTypeFromPath(uri.getPath());
|
||||
long length = inputStream.getChannel().size();
|
||||
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
|
||||
}
|
||||
case URI_TYPE_ASSET: {
|
||||
String assetPath = uri.getPath().substring(15);
|
||||
AssetFileDescriptor assetFd = null;
|
||||
InputStream inputStream;
|
||||
long length = -1;
|
||||
try {
|
||||
assetFd = assetManager.openFd(assetPath);
|
||||
inputStream = assetFd.createInputStream();
|
||||
length = assetFd.getLength();
|
||||
} catch (FileNotFoundException e) {
|
||||
// Will occur if the file is compressed.
|
||||
inputStream = assetManager.open(assetPath);
|
||||
length = inputStream.available();
|
||||
}
|
||||
String mimeType = getMimeTypeFromPath(assetPath);
|
||||
return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
|
||||
}
|
||||
case URI_TYPE_CONTENT:
|
||||
case URI_TYPE_RESOURCE: {
|
||||
String mimeType = contentResolver.getType(uri);
|
||||
AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, "r");
|
||||
InputStream inputStream = assetFd.createInputStream();
|
||||
long length = assetFd.getLength();
|
||||
return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
|
||||
}
|
||||
case URI_TYPE_DATA: {
|
||||
OpenForReadResult ret = readDataUri(uri);
|
||||
if (ret == null) {
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
case URI_TYPE_HTTP:
|
||||
case URI_TYPE_HTTPS: {
|
||||
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||
conn.setDoInput(true);
|
||||
String mimeType = conn.getHeaderField("Content-Type");
|
||||
if (mimeType != null) {
|
||||
mimeType = mimeType.split(";")[0];
|
||||
}
|
||||
int length = conn.getContentLength();
|
||||
InputStream inputStream = conn.getInputStream();
|
||||
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
|
||||
}
|
||||
case URI_TYPE_PLUGIN: {
|
||||
String pluginId = uri.getHost();
|
||||
CordovaPlugin plugin = pluginManager.getPlugin(pluginId);
|
||||
if (plugin == null) {
|
||||
throw new FileNotFoundException("Invalid plugin ID in URI: " + uri);
|
||||
}
|
||||
return plugin.handleOpenForRead(uri);
|
||||
}
|
||||
}
|
||||
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
|
||||
}
|
||||
|
||||
public OutputStream openOutputStream(Uri uri) throws IOException {
|
||||
return openOutputStream(uri, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a stream to the given URI.
|
||||
* @return Never returns null.
|
||||
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
|
||||
* resolved before being passed into this function.
|
||||
* @throws Throws an IOException if the URI cannot be opened.
|
||||
*/
|
||||
public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
|
||||
assertBackgroundThread();
|
||||
switch (getUriType(uri)) {
|
||||
case URI_TYPE_FILE: {
|
||||
File localFile = new File(uri.getPath());
|
||||
File parent = localFile.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
return new FileOutputStream(localFile, append);
|
||||
}
|
||||
case URI_TYPE_CONTENT:
|
||||
case URI_TYPE_RESOURCE: {
|
||||
AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, append ? "wa" : "w");
|
||||
return assetFd.createOutputStream();
|
||||
}
|
||||
}
|
||||
throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
|
||||
}
|
||||
|
||||
public HttpURLConnection createHttpConnection(Uri uri) throws IOException {
|
||||
assertBackgroundThread();
|
||||
return (HttpURLConnection)new URL(uri.toString()).openConnection();
|
||||
}
|
||||
|
||||
// Copies the input to the output in the most efficient manner possible.
|
||||
// Closes both streams.
|
||||
public void copyResource(OpenForReadResult input, OutputStream outputStream) throws IOException {
|
||||
assertBackgroundThread();
|
||||
try {
|
||||
InputStream inputStream = input.inputStream;
|
||||
if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
|
||||
FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
|
||||
FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
|
||||
long offset = 0;
|
||||
long length = input.length;
|
||||
if (input.assetFd != null) {
|
||||
offset = input.assetFd.getStartOffset();
|
||||
}
|
||||
// transferFrom()'s 2nd arg is a relative position. Need to set the absolute
|
||||
// position first.
|
||||
inChannel.position(offset);
|
||||
outChannel.transferFrom(inChannel, 0, length);
|
||||
} else {
|
||||
final int BUFFER_SIZE = 8192;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
for (;;) {
|
||||
int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
|
||||
|
||||
if (bytesRead <= 0) {
|
||||
break;
|
||||
}
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
input.inputStream.close();
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
|
||||
copyResource(openForRead(sourceUri), outputStream);
|
||||
}
|
||||
|
||||
// Added in 3.5.0.
|
||||
public void copyResource(Uri sourceUri, Uri dstUri) throws IOException {
|
||||
copyResource(openForRead(sourceUri), openOutputStream(dstUri));
|
||||
}
|
||||
|
||||
private void assertBackgroundThread() {
|
||||
if (threadCheckingEnabled) {
|
||||
Thread curThread = Thread.currentThread();
|
||||
if (curThread == Looper.getMainLooper().getThread()) {
|
||||
throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
|
||||
}
|
||||
if (curThread == jsThread) {
|
||||
throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getDataUriMimeType(Uri uri) {
|
||||
String uriAsString = uri.getSchemeSpecificPart();
|
||||
int commaPos = uriAsString.indexOf(',');
|
||||
if (commaPos == -1) {
|
||||
return null;
|
||||
}
|
||||
String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
|
||||
if (mimeParts.length > 0) {
|
||||
return mimeParts[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OpenForReadResult readDataUri(Uri uri) {
|
||||
String uriAsString = uri.getSchemeSpecificPart();
|
||||
int commaPos = uriAsString.indexOf(',');
|
||||
if (commaPos == -1) {
|
||||
return null;
|
||||
}
|
||||
String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
|
||||
String contentType = null;
|
||||
boolean base64 = false;
|
||||
if (mimeParts.length > 0) {
|
||||
contentType = mimeParts[0];
|
||||
}
|
||||
for (int i = 1; i < mimeParts.length; ++i) {
|
||||
if ("base64".equalsIgnoreCase(mimeParts[i])) {
|
||||
base64 = true;
|
||||
}
|
||||
}
|
||||
String dataPartAsString = uriAsString.substring(commaPos + 1);
|
||||
byte[] data;
|
||||
if (base64) {
|
||||
data = Base64.decode(dataPartAsString, Base64.DEFAULT);
|
||||
} else {
|
||||
try {
|
||||
data = dataPartAsString.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
data = dataPartAsString.getBytes();
|
||||
}
|
||||
}
|
||||
InputStream inputStream = new ByteArrayInputStream(data);
|
||||
return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
|
||||
}
|
||||
|
||||
private static void assertNonRelative(Uri uri) {
|
||||
if (!uri.isAbsolute()) {
|
||||
throw new IllegalArgumentException("Relative URIs are not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public static final class OpenForReadResult {
|
||||
public final Uri uri;
|
||||
public final InputStream inputStream;
|
||||
public final String mimeType;
|
||||
public final long length;
|
||||
public final AssetFileDescriptor assetFd;
|
||||
|
||||
public OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
|
||||
this.uri = uri;
|
||||
this.inputStream = inputStream;
|
||||
this.mimeType = mimeType;
|
||||
this.length = length;
|
||||
this.assetFd = assetFd;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
|
||||
/**
|
||||
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
|
||||
* This is an interface so that it can be easily mocked in tests.
|
||||
* Methods may be added to this interface without a major version bump, as plugins & embedders
|
||||
* are not expected to implement it.
|
||||
*/
|
||||
public interface CordovaWebView {
|
||||
public static final String CORDOVA_VERSION = "7.1.3";
|
||||
|
||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||
|
||||
boolean isInitialized();
|
||||
|
||||
View getView();
|
||||
|
||||
void loadUrlIntoView(String url, boolean recreatePlugins);
|
||||
|
||||
void stopLoading();
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
void clearCache();
|
||||
|
||||
/** Use parameter-less overload */
|
||||
@Deprecated
|
||||
void clearCache(boolean b);
|
||||
|
||||
void clearHistory();
|
||||
|
||||
boolean backHistory();
|
||||
|
||||
void handlePause(boolean keepRunning);
|
||||
|
||||
void onNewIntent(Intent intent);
|
||||
|
||||
void handleResume(boolean keepRunning);
|
||||
|
||||
void handleStart();
|
||||
|
||||
void handleStop();
|
||||
|
||||
void handleDestroy();
|
||||
|
||||
/**
|
||||
* Send JavaScript statement back to JavaScript.
|
||||
*
|
||||
* Deprecated (https://issues.apache.org/jira/browse/CB-6851)
|
||||
* Instead of executing snippets of JS, you should use the exec bridge
|
||||
* to create a Java->JS communication channel.
|
||||
* To do this:
|
||||
* 1. Within plugin.xml (to have your JS run before deviceready):
|
||||
* <js-module><runs/></js-module>
|
||||
* 2. Within your .js (call exec on start-up):
|
||||
* require('cordova/channel').onCordovaReady.subscribe(function() {
|
||||
* require('cordova/exec')(win, null, 'Plugin', 'method', []);
|
||||
* function win(message) {
|
||||
* ... process message from java here ...
|
||||
* }
|
||||
* });
|
||||
* 3. Within your .java:
|
||||
* PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
|
||||
* dataResult.setKeepCallback(true);
|
||||
* savedCallbackContext.sendPluginResult(dataResult);
|
||||
*/
|
||||
@Deprecated
|
||||
void sendJavascript(String statememt);
|
||||
|
||||
/**
|
||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||
*
|
||||
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
|
||||
*
|
||||
* @param url The url to load.
|
||||
* @param openExternal Load url in browser instead of Cordova webview.
|
||||
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||
* @param params Parameters for new app
|
||||
*/
|
||||
void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCustomViewShowing();
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
void showCustomView(View view, CustomViewCallback callback);
|
||||
|
||||
/**
|
||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||
*/
|
||||
@Deprecated
|
||||
void hideCustomView();
|
||||
|
||||
CordovaResourceApi getResourceApi();
|
||||
|
||||
void setButtonPlumbedToJs(int keyCode, boolean override);
|
||||
boolean isButtonPlumbedToJs(int keyCode);
|
||||
|
||||
void sendPluginResult(PluginResult cr, String callbackId);
|
||||
|
||||
PluginManager getPluginManager();
|
||||
CordovaWebViewEngine getEngine();
|
||||
CordovaPreferences getPreferences();
|
||||
ICordovaCookieManager getCookieManager();
|
||||
|
||||
String getUrl();
|
||||
|
||||
// TODO: Work on deleting these by removing refs from plugins.
|
||||
Context getContext();
|
||||
void loadUrl(String url);
|
||||
Object postMessage(String id, Object data);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
/**
|
||||
* Interface for all Cordova engines.
|
||||
* No methods will be added to this class (in order to be compatible with existing engines).
|
||||
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
|
||||
*/
|
||||
public interface CordovaWebViewEngine {
|
||||
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue);
|
||||
|
||||
CordovaWebView getCordovaWebView();
|
||||
ICordovaCookieManager getCookieManager();
|
||||
View getView();
|
||||
|
||||
void loadUrl(String url, boolean clearNavigationStack);
|
||||
|
||||
void stopLoading();
|
||||
|
||||
/** Return the currently loaded URL */
|
||||
String getUrl();
|
||||
|
||||
void clearCache();
|
||||
|
||||
/** After calling clearHistory(), canGoBack() should be false. */
|
||||
void clearHistory();
|
||||
|
||||
boolean canGoBack();
|
||||
|
||||
/** Returns whether a navigation occurred */
|
||||
boolean goBack();
|
||||
|
||||
/** Pauses / resumes the WebView's event loop. */
|
||||
void setPaused(boolean value);
|
||||
|
||||
/** Clean up all resources associated with the WebView. */
|
||||
void destroy();
|
||||
|
||||
/** Add the evaulate Javascript method **/
|
||||
void evaluateJavascript(String js, ValueCallback<String> callback);
|
||||
|
||||
/**
|
||||
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
|
||||
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
|
||||
*/
|
||||
public interface EngineView {
|
||||
CordovaWebView getCordovaWebView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
|
||||
* Methods may be added in future cordova versions, but never removed.
|
||||
*/
|
||||
public interface Client {
|
||||
Boolean onDispatchKeyEvent(KeyEvent event);
|
||||
void clearLoadTimeoutTimer();
|
||||
void onPageStarted(String newUrl);
|
||||
void onReceivedError(int errorCode, String description, String failingUrl);
|
||||
void onPageFinishedLoading(String url);
|
||||
boolean onNavigationAttempt(String url);
|
||||
}
|
||||
}
|
@ -0,0 +1,615 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
|
||||
* Class uses two-phase initialization. You must call init() before calling any other methods.
|
||||
*/
|
||||
public class CordovaWebViewImpl implements CordovaWebView {
|
||||
|
||||
public static final String TAG = "CordovaWebViewImpl";
|
||||
|
||||
private PluginManager pluginManager;
|
||||
|
||||
protected final CordovaWebViewEngine engine;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
// Flag to track that a loadUrl timeout occurred
|
||||
private int loadUrlTimeout = 0;
|
||||
|
||||
private CordovaResourceApi resourceApi;
|
||||
private CordovaPreferences preferences;
|
||||
private CoreAndroid appPlugin;
|
||||
private NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||
private EngineClient engineClient = new EngineClient();
|
||||
private boolean hasPausedEver;
|
||||
|
||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||
String loadedUrl;
|
||||
|
||||
/** custom view created by the browser (a video player for example) */
|
||||
private View mCustomView;
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
|
||||
private Set<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||
|
||||
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
|
||||
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
|
||||
try {
|
||||
Class<?> webViewClass = Class.forName(className);
|
||||
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
|
||||
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create webview. ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public CordovaWebViewImpl(CordovaWebViewEngine cordovaWebViewEngine) {
|
||||
this.engine = cordovaWebViewEngine;
|
||||
}
|
||||
|
||||
// Convenience method for when creating programmatically (not from Config.xml).
|
||||
public void init(CordovaInterface cordova) {
|
||||
init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
|
||||
}
|
||||
|
||||
@SuppressLint("Assert")
|
||||
@Override
|
||||
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.cordova = cordova;
|
||||
this.preferences = preferences;
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
|
||||
nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
|
||||
|
||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||
// This isn't enforced by the compiler, so assert here.
|
||||
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
|
||||
|
||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
||||
pluginManager.init();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return cordova != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||
engine.loadUrl(url, false);
|
||||
return;
|
||||
}
|
||||
|
||||
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||
|
||||
if (recreatePlugins) {
|
||||
// Don't re-initialize on first load.
|
||||
if (loadedUrl != null) {
|
||||
appPlugin = null;
|
||||
pluginManager.init();
|
||||
}
|
||||
loadedUrl = url;
|
||||
}
|
||||
|
||||
// Create a timeout timer for loadUrl
|
||||
final int currentLoadUrlTimeout = loadUrlTimeout;
|
||||
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||
|
||||
// Timeout error method
|
||||
final Runnable loadError = new Runnable() {
|
||||
public void run() {
|
||||
stopLoading();
|
||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||
|
||||
// Handle other errors by passing them to the webview in JS
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", -6);
|
||||
data.put("description", "The connection to the server was unsuccessful.");
|
||||
data.put("url", url);
|
||||
} catch (JSONException e) {
|
||||
// Will never happen.
|
||||
}
|
||||
pluginManager.postMessage("onReceivedError", data);
|
||||
}
|
||||
};
|
||||
|
||||
// Timeout timer method
|
||||
final Runnable timeoutCheck = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(loadUrlTimeoutValue);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// If timeout, then stop loading and handle error
|
||||
if (loadUrlTimeout == currentLoadUrlTimeout) {
|
||||
cordova.getActivity().runOnUiThread(loadError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final boolean _recreatePlugins = recreatePlugins;
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (loadUrlTimeoutValue > 0) {
|
||||
cordova.getThreadPool().execute(timeoutCheck);
|
||||
}
|
||||
engine.loadUrl(url, _recreatePlugins);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void loadUrl(String url) {
|
||||
loadUrlIntoView(url, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
|
||||
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap)", url, openExternal, clearHistory);
|
||||
|
||||
// If clearing history
|
||||
if (clearHistory) {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
// If loading into our webview
|
||||
if (!openExternal) {
|
||||
// Make sure url is in whitelist
|
||||
if (pluginManager.shouldAllowNavigation(url)) {
|
||||
// TODO: What about params?
|
||||
// Load new URL
|
||||
loadUrlIntoView(url, true);
|
||||
} else {
|
||||
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
|
||||
}
|
||||
}
|
||||
if (!pluginManager.shouldOpenExternalUrl(url)) {
|
||||
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
// To send an intent without CATEGORY_BROWSER, a custom plugin should be used.
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
Uri uri = Uri.parse(url);
|
||||
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
||||
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||
} else {
|
||||
intent.setData(uri);
|
||||
}
|
||||
cordova.getActivity().startActivity(intent);
|
||||
} catch (android.content.ActivityNotFoundException e) {
|
||||
LOG.e(TAG, "Error loading url " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
LOG.d(TAG, "showing Custom View");
|
||||
// if a view already exists then immediately terminate the new one
|
||||
if (mCustomView != null) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the view and its callback for later (to kill it properly)
|
||||
mCustomView = view;
|
||||
mCustomViewCallback = callback;
|
||||
|
||||
// Add the custom view to its container.
|
||||
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||
parent.addView(view, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Gravity.CENTER));
|
||||
|
||||
// Hide the content view.
|
||||
engine.getView().setVisibility(View.GONE);
|
||||
|
||||
// Finally show the custom view container.
|
||||
parent.setVisibility(View.VISIBLE);
|
||||
parent.bringToFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void hideCustomView() {
|
||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||
if (mCustomView == null) return;
|
||||
LOG.d(TAG, "Hiding Custom View");
|
||||
|
||||
// Hide the custom view.
|
||||
mCustomView.setVisibility(View.GONE);
|
||||
|
||||
// Remove the custom view from its container.
|
||||
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||
parent.removeView(mCustomView);
|
||||
mCustomView = null;
|
||||
mCustomViewCallback.onCustomViewHidden();
|
||||
|
||||
// Show the content view.
|
||||
engine.getView().setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean isCustomViewShowing() {
|
||||
return mCustomView != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void sendJavascript(String statement) {
|
||||
nativeToJsMessageQueue.addJavaScript(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginResult(PluginResult cr, String callbackId) {
|
||||
nativeToJsMessageQueue.addPluginResult(cr, callbackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
return pluginManager;
|
||||
}
|
||||
@Override
|
||||
public CordovaPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return engine.getCookieManager();
|
||||
}
|
||||
@Override
|
||||
public CordovaResourceApi getResourceApi() {
|
||||
return resourceApi;
|
||||
}
|
||||
@Override
|
||||
public CordovaWebViewEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
@Override
|
||||
public View getView() {
|
||||
return engine.getView();
|
||||
}
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return engine.getView().getContext();
|
||||
}
|
||||
|
||||
private void sendJavascriptEvent(String event) {
|
||||
if (appPlugin == null) {
|
||||
appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
}
|
||||
|
||||
if (appPlugin == null) {
|
||||
LOG.w(TAG, "Unable to fire event without existing plugin");
|
||||
return;
|
||||
}
|
||||
appPlugin.fireJavascriptEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
// TODO: Why are search and menu buttons handled separately?
|
||||
if (override) {
|
||||
boundKeyCodes.add(keyCode);
|
||||
} else {
|
||||
boundKeyCodes.remove(keyCode);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isButtonPlumbedToJs(int keyCode) {
|
||||
return boundKeyCodes.contains(keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postMessage(String id, Object data) {
|
||||
return pluginManager.postMessage(id, data);
|
||||
}
|
||||
|
||||
// Engine method proxies:
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return engine.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
// Clear timeout flag
|
||||
loadUrlTimeout++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoBack() {
|
||||
return engine.canGoBack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
engine.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void clearCache(boolean b) {
|
||||
engine.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory() {
|
||||
engine.clearHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backHistory() {
|
||||
return engine.goBack();
|
||||
}
|
||||
|
||||
/////// LifeCycle methods ///////
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (this.pluginManager != null) {
|
||||
this.pluginManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handlePause(boolean keepRunning) {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
hasPausedEver = true;
|
||||
pluginManager.onPause(keepRunning);
|
||||
sendJavascriptEvent("pause");
|
||||
|
||||
// If app doesn't want to run in background
|
||||
if (!keepRunning) {
|
||||
// Pause JavaScript timers. This affects all webviews within the app!
|
||||
engine.setPaused(true);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleResume(boolean keepRunning) {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume JavaScript timers. This affects all webviews within the app!
|
||||
engine.setPaused(false);
|
||||
this.pluginManager.onResume(keepRunning);
|
||||
|
||||
// In order to match the behavior of the other platforms, we only send onResume after an
|
||||
// onPause has occurred. The resume event might still be sent if the Activity was killed
|
||||
// while waiting for the result of an external Activity once the result is obtained
|
||||
if (hasPausedEver) {
|
||||
sendJavascriptEvent("resume");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void handleStart() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
pluginManager.onStart();
|
||||
}
|
||||
@Override
|
||||
public void handleStop() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
pluginManager.onStop();
|
||||
}
|
||||
@Override
|
||||
public void handleDestroy() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
// Cancel pending timeout timer.
|
||||
loadUrlTimeout++;
|
||||
|
||||
// Forward to plugins
|
||||
this.pluginManager.onDestroy();
|
||||
|
||||
// TODO: about:blank is a bit special (and the default URL for new frames)
|
||||
// We should use a blank data: url instead so it's more obvious
|
||||
this.loadUrl("about:blank");
|
||||
|
||||
// TODO: Should not destroy webview until after about:blank is done loading.
|
||||
engine.destroy();
|
||||
hideCustomView();
|
||||
}
|
||||
|
||||
protected class EngineClient implements CordovaWebViewEngine.Client {
|
||||
@Override
|
||||
public void clearLoadTimeoutTimer() {
|
||||
loadUrlTimeout++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(String newUrl) {
|
||||
LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")");
|
||||
boundKeyCodes.clear();
|
||||
pluginManager.onReset();
|
||||
pluginManager.postMessage("onPageStarted", newUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(int errorCode, String description, String failingUrl) {
|
||||
clearLoadTimeoutTimer();
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("errorCode", errorCode);
|
||||
data.put("description", description);
|
||||
data.put("url", failingUrl);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
pluginManager.postMessage("onReceivedError", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinishedLoading(String url) {
|
||||
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||
|
||||
clearLoadTimeoutTimer();
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
pluginManager.postMessage("onPageFinished", url);
|
||||
|
||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||
if (engine.getView().getVisibility() != View.VISIBLE) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pluginManager.postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
// Shutdown if blank loaded
|
||||
if (url.equals("about:blank")) {
|
||||
pluginManager.postMessage("exit", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean onDispatchKeyEvent(KeyEvent event) {
|
||||
int keyCode = event.getKeyCode();
|
||||
boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (isBackButton && mCustomView != null) {
|
||||
return true;
|
||||
} else if (boundKeyCodes.contains(keyCode)) {
|
||||
return true;
|
||||
} else if (isBackButton) {
|
||||
return engine.canGoBack();
|
||||
}
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
if (isBackButton && mCustomView != null) {
|
||||
hideCustomView();
|
||||
return true;
|
||||
} else if (boundKeyCodes.contains(keyCode)) {
|
||||
String eventName = null;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
eventName = "volumedownbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
eventName = "volumeupbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_SEARCH:
|
||||
eventName = "searchbutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
eventName = "menubutton";
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
eventName = "backbutton";
|
||||
break;
|
||||
}
|
||||
if (eventName != null) {
|
||||
sendJavascriptEvent(eventName);
|
||||
return true;
|
||||
}
|
||||
} else if (isBackButton) {
|
||||
return engine.goBack();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationAttempt(String url) {
|
||||
// Give plugins the chance to handle the url
|
||||
if (pluginManager.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
} else if (pluginManager.shouldAllowNavigation(url)) {
|
||||
return false;
|
||||
} else if (pluginManager.shouldOpenExternalUrl(url)) {
|
||||
showWebPage(url, true, false, null);
|
||||
return true;
|
||||
}
|
||||
LOG.w(TAG, "Blocked (possibly sub-frame) navigation to non-allowed URL: " + url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,390 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class exposes methods in Cordova that can be called from JavaScript.
|
||||
*/
|
||||
public class CoreAndroid extends CordovaPlugin {
|
||||
|
||||
public static final String PLUGIN_NAME = "CoreAndroid";
|
||||
protected static final String TAG = "CordovaApp";
|
||||
private BroadcastReceiver telephonyReceiver;
|
||||
private CallbackContext messageChannel;
|
||||
private PluginResult pendingResume;
|
||||
private final Object messageChannelLock = new Object();
|
||||
|
||||
/**
|
||||
* Send an event to be fired on the Javascript side.
|
||||
*
|
||||
* @param action The name of the event to be fired
|
||||
*/
|
||||
public void fireJavascriptEvent(String action) {
|
||||
sendEventMessage(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the context of the Command. This can then be used to do things like
|
||||
* get file paths associated with the Activity.
|
||||
*/
|
||||
@Override
|
||||
public void pluginInitialize() {
|
||||
this.initTelephonyReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request and returns PluginResult.
|
||||
*
|
||||
* @param action The action to execute.
|
||||
* @param args JSONArry of arguments for the plugin.
|
||||
* @param callbackContext The callback context from which we were invoked.
|
||||
* @return A PluginResult object with a status and message.
|
||||
*/
|
||||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
||||
PluginResult.Status status = PluginResult.Status.OK;
|
||||
String result = "";
|
||||
|
||||
try {
|
||||
if (action.equals("clearCache")) {
|
||||
this.clearCache();
|
||||
}
|
||||
else if (action.equals("show")) {
|
||||
// This gets called from JavaScript onCordovaReady to show the webview.
|
||||
// I recommend we change the name of the Message as spinner/stop is not
|
||||
// indicative of what this actually does (shows the webview).
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.getPluginManager().postMessage("spinner", "stop");
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (action.equals("loadUrl")) {
|
||||
this.loadUrl(args.getString(0), args.optJSONObject(1));
|
||||
}
|
||||
else if (action.equals("cancelLoadUrl")) {
|
||||
//this.cancelLoadUrl();
|
||||
}
|
||||
else if (action.equals("clearHistory")) {
|
||||
this.clearHistory();
|
||||
}
|
||||
else if (action.equals("backHistory")) {
|
||||
this.backHistory();
|
||||
}
|
||||
else if (action.equals("overrideButton")) {
|
||||
this.overrideButton(args.getString(0), args.getBoolean(1));
|
||||
}
|
||||
else if (action.equals("overrideBackbutton")) {
|
||||
this.overrideBackbutton(args.getBoolean(0));
|
||||
}
|
||||
else if (action.equals("exitApp")) {
|
||||
this.exitApp();
|
||||
}
|
||||
else if (action.equals("messageChannel")) {
|
||||
synchronized(messageChannelLock) {
|
||||
messageChannel = callbackContext;
|
||||
if (pendingResume != null) {
|
||||
sendEventMessage(pendingResume);
|
||||
pendingResume = null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
callbackContext.sendPluginResult(new PluginResult(status, result));
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// LOCAL METHODS
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Clear the resource cache.
|
||||
*/
|
||||
public void clearCache() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.clearCache(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*
|
||||
* @param url
|
||||
* @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
|
||||
* @throws JSONException
|
||||
*/
|
||||
public void loadUrl(String url, JSONObject props) throws JSONException {
|
||||
LOG.d("App", "App.loadUrl("+url+","+props+")");
|
||||
int wait = 0;
|
||||
boolean openExternal = false;
|
||||
boolean clearHistory = false;
|
||||
|
||||
// If there are properties, then set them on the Activity
|
||||
HashMap<String, Object> params = new HashMap<String, Object>();
|
||||
if (props != null) {
|
||||
JSONArray keys = props.names();
|
||||
for (int i = 0; i < keys.length(); i++) {
|
||||
String key = keys.getString(i);
|
||||
if (key.equals("wait")) {
|
||||
wait = props.getInt(key);
|
||||
}
|
||||
else if (key.equalsIgnoreCase("openexternal")) {
|
||||
openExternal = props.getBoolean(key);
|
||||
}
|
||||
else if (key.equalsIgnoreCase("clearhistory")) {
|
||||
clearHistory = props.getBoolean(key);
|
||||
}
|
||||
else {
|
||||
Object value = props.get(key);
|
||||
if (value == null) {
|
||||
|
||||
}
|
||||
else if (value.getClass().equals(String.class)) {
|
||||
params.put(key, (String)value);
|
||||
}
|
||||
else if (value.getClass().equals(Boolean.class)) {
|
||||
params.put(key, (Boolean)value);
|
||||
}
|
||||
else if (value.getClass().equals(Integer.class)) {
|
||||
params.put(key, (Integer)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If wait property, then delay loading
|
||||
|
||||
if (wait > 0) {
|
||||
try {
|
||||
synchronized(this) {
|
||||
this.wait(wait);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.webView.showWebPage(url, openExternal, clearHistory, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear page history for the app.
|
||||
*/
|
||||
public void clearHistory() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.clearHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page displayed.
|
||||
* This is the same as pressing the backbutton on Android device.
|
||||
*/
|
||||
public void backHistory() {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
webView.backHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android back button.
|
||||
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
|
||||
*
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
public void overrideBackbutton(boolean override) {
|
||||
LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android volume buttons.
|
||||
* If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
|
||||
*
|
||||
* @param button volumeup, volumedown
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
public void overrideButton(String button, boolean override) {
|
||||
LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
|
||||
if (button.equals("volumeup")) {
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
|
||||
}
|
||||
else if (button.equals("volumedown")) {
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
|
||||
}
|
||||
else if (button.equals("menubutton")) {
|
||||
webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the Android back button is overridden by the user.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isBackbuttonOverridden() {
|
||||
return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the Android application.
|
||||
*/
|
||||
public void exitApp() {
|
||||
this.webView.getPluginManager().postMessage("exit", null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listen for telephony events: RINGING, OFFHOOK and IDLE
|
||||
* Send these events to all plugins using
|
||||
* CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle")
|
||||
*/
|
||||
private void initTelephonyReceiver() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
||||
//final CordovaInterface mycordova = this.cordova;
|
||||
this.telephonyReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
// If state has changed
|
||||
if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
|
||||
if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) {
|
||||
String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
|
||||
if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
|
||||
LOG.i(TAG, "Telephone RINGING");
|
||||
webView.getPluginManager().postMessage("telephone", "ringing");
|
||||
}
|
||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
|
||||
LOG.i(TAG, "Telephone OFFHOOK");
|
||||
webView.getPluginManager().postMessage("telephone", "offhook");
|
||||
}
|
||||
else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
|
||||
LOG.i(TAG, "Telephone IDLE");
|
||||
webView.getPluginManager().postMessage("telephone", "idle");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Register the receiver
|
||||
webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
|
||||
}
|
||||
|
||||
private void sendEventMessage(String action) {
|
||||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("action", action);
|
||||
} catch (JSONException e) {
|
||||
LOG.e(TAG, "Failed to create event message", e);
|
||||
}
|
||||
sendEventMessage(new PluginResult(PluginResult.Status.OK, obj));
|
||||
}
|
||||
|
||||
private void sendEventMessage(PluginResult payload) {
|
||||
payload.setKeepCallback(true);
|
||||
if (messageChannel != null) {
|
||||
messageChannel.sendPluginResult(payload);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unregister the receiver
|
||||
*
|
||||
*/
|
||||
public void onDestroy()
|
||||
{
|
||||
webView.getContext().unregisterReceiver(this.telephonyReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to send the resume event in the case that the Activity is destroyed by the OS
|
||||
*
|
||||
* @param resumeEvent PluginResult containing the payload for the resume event to be fired
|
||||
*/
|
||||
public void sendResumeEvent(PluginResult resumeEvent) {
|
||||
// This operation must be synchronized because plugin results that trigger resume
|
||||
// events can be processed asynchronously
|
||||
synchronized(messageChannelLock) {
|
||||
if (messageChannel != null) {
|
||||
sendEventMessage(resumeEvent);
|
||||
} else {
|
||||
// Might get called before the page loads, so we need to store it until the
|
||||
// messageChannel gets created
|
||||
this.pendingResume = resumeEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This needs to be implemented if you wish to use the Camera Plugin or other plugins
|
||||
* that read the Build Configuration.
|
||||
*
|
||||
* Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
|
||||
* StackOverflow. This is annoying as hell!
|
||||
*
|
||||
*/
|
||||
|
||||
public static Object getBuildConfigValue(Context ctx, String key)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
|
||||
Field field = clazz.getField(key);
|
||||
return field.get(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
/*
|
||||
* Any exposed Javascript API MUST implement these three things!
|
||||
*/
|
||||
public interface ExposedJsApi {
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* Specifies interface for handling certificate requests.
|
||||
*/
|
||||
public interface ICordovaClientCertRequest {
|
||||
/**
|
||||
* Cancel this request
|
||||
*/
|
||||
public void cancel();
|
||||
|
||||
/*
|
||||
* Returns the host name of the server requesting the certificate.
|
||||
*/
|
||||
public String getHost();
|
||||
|
||||
/*
|
||||
* Returns the acceptable types of asymmetric keys (can be null).
|
||||
*/
|
||||
public String[] getKeyTypes();
|
||||
|
||||
/*
|
||||
* Returns the port number of the server requesting the certificate.
|
||||
*/
|
||||
public int getPort();
|
||||
|
||||
/*
|
||||
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
|
||||
*/
|
||||
public Principal[] getPrincipals();
|
||||
|
||||
/*
|
||||
* Ignore the request for now. Do not remember user's choice.
|
||||
*/
|
||||
public void ignore();
|
||||
|
||||
/*
|
||||
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
|
||||
*
|
||||
* @param privateKey The privateKey
|
||||
* @param chain The certificate chain
|
||||
*/
|
||||
public void proceed(PrivateKey privateKey, X509Certificate[] chain);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
public interface ICordovaCookieManager {
|
||||
|
||||
public void setCookiesEnabled(boolean accept);
|
||||
|
||||
public void setCookie(final String url, final String value);
|
||||
|
||||
public String getCookie(final String url);
|
||||
|
||||
public void clearCookies();
|
||||
|
||||
public void flush();
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
/**
|
||||
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
|
||||
* specifying user credentials.
|
||||
*/
|
||||
public interface ICordovaHttpAuthHandler {
|
||||
/**
|
||||
* Instructs the WebView to cancel the authentication request.
|
||||
*/
|
||||
public void cancel ();
|
||||
|
||||
/**
|
||||
* Instructs the WebView to proceed with the authentication with the given credentials.
|
||||
*
|
||||
* @param username The user name
|
||||
* @param password The password
|
||||
*/
|
||||
public void proceed (String username, String password);
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Log to Android logging system.
|
||||
*
|
||||
* Log message can be a string or a printf formatted string with arguments.
|
||||
* See http://developer.android.com/reference/java/util/Formatter.html
|
||||
*/
|
||||
public class LOG {
|
||||
|
||||
public static final int VERBOSE = Log.VERBOSE;
|
||||
public static final int DEBUG = Log.DEBUG;
|
||||
public static final int INFO = Log.INFO;
|
||||
public static final int WARN = Log.WARN;
|
||||
public static final int ERROR = Log.ERROR;
|
||||
|
||||
// Current log level
|
||||
public static int LOGLEVEL = Log.ERROR;
|
||||
|
||||
/**
|
||||
* Set the current log level.
|
||||
*
|
||||
* @param logLevel
|
||||
*/
|
||||
public static void setLogLevel(int logLevel) {
|
||||
LOGLEVEL = logLevel;
|
||||
Log.i("CordovaLog", "Changing log level to " + logLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current log level.
|
||||
*
|
||||
* @param logLevel
|
||||
*/
|
||||
public static void setLogLevel(String logLevel) {
|
||||
if ("VERBOSE".equals(logLevel)) LOGLEVEL = VERBOSE;
|
||||
else if ("DEBUG".equals(logLevel)) LOGLEVEL = DEBUG;
|
||||
else if ("INFO".equals(logLevel)) LOGLEVEL = INFO;
|
||||
else if ("WARN".equals(logLevel)) LOGLEVEL = WARN;
|
||||
else if ("ERROR".equals(logLevel)) LOGLEVEL = ERROR;
|
||||
Log.i("CordovaLog", "Changing log level to " + logLevel + "(" + LOGLEVEL + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if log level will be logged
|
||||
*
|
||||
* @param logLevel
|
||||
* @return true if the parameter passed in is greater than or equal to the current log level
|
||||
*/
|
||||
public static boolean isLoggable(int logLevel) {
|
||||
return (logLevel >= LOGLEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void v(String tag, String s) {
|
||||
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void d(String tag, String s) {
|
||||
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Info log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void i(String tag, String s) {
|
||||
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void w(String tag, String s) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
*/
|
||||
public static void e(String tag, String s) {
|
||||
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void v(String tag, String s, Throwable e) {
|
||||
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void d(String tag, String s, Throwable e) {
|
||||
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Info log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void i(String tag, String s, Throwable e) {
|
||||
if (LOG.INFO >= LOGLEVEL) Log.i(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param e
|
||||
*/
|
||||
public static void w(String tag, Throwable e) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void w(String tag, String s, Throwable e) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log message.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param e
|
||||
*/
|
||||
public static void e(String tag, String s, Throwable e) {
|
||||
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbose log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void v(String tag, String s, Object... args) {
|
||||
if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void d(String tag, String s, Object... args) {
|
||||
if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Info log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void i(String tag, String s, Object... args) {
|
||||
if (LOG.INFO >= LOGLEVEL) Log.i(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void w(String tag, String s, Object... args) {
|
||||
if (LOG.WARN >= LOGLEVEL) Log.w(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Error log message with printf formatting.
|
||||
*
|
||||
* @param tag
|
||||
* @param s
|
||||
* @param args
|
||||
*/
|
||||
public static void e(String tag, String s, Object... args) {
|
||||
if (LOG.ERROR >= LOGLEVEL) Log.e(tag, String.format(s, args));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,542 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Holds the list of messages to be sent to the WebView.
|
||||
*/
|
||||
public class NativeToJsMessageQueue {
|
||||
private static final String LOG_TAG = "JsMessageQueue";
|
||||
|
||||
// Set this to true to force plugin results to be encoding as
|
||||
// JS instead of the custom format (useful for benchmarking).
|
||||
// Doesn't work for multipart messages.
|
||||
private static final boolean FORCE_ENCODE_USING_EVAL = false;
|
||||
|
||||
// Disable sending back native->JS messages during an exec() when the active
|
||||
// exec() is asynchronous. Set this to true when running bridge benchmarks.
|
||||
static final boolean DISABLE_EXEC_CHAINING = false;
|
||||
|
||||
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
|
||||
// This currently only chops up on message boundaries. It may be useful
|
||||
// to allow it to break up messages.
|
||||
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
|
||||
|
||||
/**
|
||||
* When true, the active listener is not fired upon enqueue. When set to false,
|
||||
* the active listener will be fired if the queue is non-empty.
|
||||
*/
|
||||
private boolean paused;
|
||||
|
||||
/**
|
||||
* The list of JavaScript statements to be sent to JavaScript.
|
||||
*/
|
||||
private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
|
||||
|
||||
/**
|
||||
* The array of listeners that can be used to send messages to JS.
|
||||
*/
|
||||
private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>();
|
||||
|
||||
/**
|
||||
* When null, the bridge is disabled. This occurs during page transitions.
|
||||
* When disabled, all callbacks are dropped since they are assumed to be
|
||||
* relevant to the previous page.
|
||||
*/
|
||||
private BridgeMode activeBridgeMode;
|
||||
|
||||
public void addBridgeMode(BridgeMode bridgeMode) {
|
||||
bridgeModes.add(bridgeMode);
|
||||
}
|
||||
|
||||
public boolean isBridgeEnabled() {
|
||||
return activeBridgeMode != null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return queue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the bridge mode.
|
||||
*/
|
||||
public void setBridgeMode(int value) {
|
||||
if (value < -1 || value >= bridgeModes.size()) {
|
||||
LOG.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||
} else {
|
||||
BridgeMode newMode = value < 0 ? null : bridgeModes.get(value);
|
||||
if (newMode != activeBridgeMode) {
|
||||
LOG.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
||||
synchronized (this) {
|
||||
activeBridgeMode = newMode;
|
||||
if (newMode != null) {
|
||||
newMode.reset();
|
||||
if (!paused && !queue.isEmpty()) {
|
||||
newMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all messages and resets to the default bridge mode.
|
||||
*/
|
||||
public void reset() {
|
||||
synchronized (this) {
|
||||
queue.clear();
|
||||
setBridgeMode(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePackedMessageLength(JsMessage message) {
|
||||
int messageLen = message.calculateEncodedLength();
|
||||
String messageLenStr = String.valueOf(messageLen);
|
||||
return messageLenStr.length() + messageLen + 1;
|
||||
}
|
||||
|
||||
private void packMessage(JsMessage message, StringBuilder sb) {
|
||||
int len = message.calculateEncodedLength();
|
||||
sb.append(len)
|
||||
.append(' ');
|
||||
message.encodeAsMessage(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and returns queued messages combined into a single string.
|
||||
* Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
|
||||
* Returns null if the queue is empty.
|
||||
*/
|
||||
public String popAndEncode(boolean fromOnlineEvent) {
|
||||
synchronized (this) {
|
||||
if (activeBridgeMode == null) {
|
||||
return null;
|
||||
}
|
||||
activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
|
||||
if (queue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int totalPayloadLen = 0;
|
||||
int numMessagesToSend = 0;
|
||||
for (JsMessage message : queue) {
|
||||
int messageSize = calculatePackedMessageLength(message);
|
||||
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
|
||||
break;
|
||||
}
|
||||
totalPayloadLen += messageSize;
|
||||
numMessagesToSend += 1;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(totalPayloadLen);
|
||||
for (int i = 0; i < numMessagesToSend; ++i) {
|
||||
JsMessage message = queue.removeFirst();
|
||||
packMessage(message, sb);
|
||||
}
|
||||
|
||||
if (!queue.isEmpty()) {
|
||||
// Attach a char to indicate that there are more messages pending.
|
||||
sb.append('*');
|
||||
}
|
||||
String ret = sb.toString();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as popAndEncode(), except encodes in a form that can be executed as JS.
|
||||
*/
|
||||
public String popAndEncodeAsJs() {
|
||||
synchronized (this) {
|
||||
int length = queue.size();
|
||||
if (length == 0) {
|
||||
return null;
|
||||
}
|
||||
int totalPayloadLen = 0;
|
||||
int numMessagesToSend = 0;
|
||||
for (JsMessage message : queue) {
|
||||
int messageSize = message.calculateEncodedLength() + 50; // overestimate.
|
||||
if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
|
||||
break;
|
||||
}
|
||||
totalPayloadLen += messageSize;
|
||||
numMessagesToSend += 1;
|
||||
}
|
||||
boolean willSendAllMessages = numMessagesToSend == queue.size();
|
||||
StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
|
||||
// Wrap each statement in a try/finally so that if one throws it does
|
||||
// not affect the next.
|
||||
for (int i = 0; i < numMessagesToSend; ++i) {
|
||||
JsMessage message = queue.removeFirst();
|
||||
if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
|
||||
message.encodeAsJsMessage(sb);
|
||||
} else {
|
||||
sb.append("try{");
|
||||
message.encodeAsJsMessage(sb);
|
||||
sb.append("}finally{");
|
||||
}
|
||||
}
|
||||
if (!willSendAllMessages) {
|
||||
sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
|
||||
}
|
||||
for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
|
||||
sb.append('}');
|
||||
}
|
||||
String ret = sb.toString();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript statement to the list.
|
||||
*/
|
||||
public void addJavaScript(String statement) {
|
||||
enqueueMessage(new JsMessage(statement));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript statement to the list.
|
||||
*/
|
||||
public void addPluginResult(PluginResult result, String callbackId) {
|
||||
if (callbackId == null) {
|
||||
LOG.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
|
||||
return;
|
||||
}
|
||||
// Don't send anything if there is no result and there is no need to
|
||||
// clear the callbacks.
|
||||
boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
|
||||
boolean keepCallback = result.getKeepCallback();
|
||||
if (noResult && keepCallback) {
|
||||
return;
|
||||
}
|
||||
JsMessage message = new JsMessage(result, callbackId);
|
||||
if (FORCE_ENCODE_USING_EVAL) {
|
||||
StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
|
||||
message.encodeAsJsMessage(sb);
|
||||
message = new JsMessage(sb.toString());
|
||||
}
|
||||
|
||||
enqueueMessage(message);
|
||||
}
|
||||
|
||||
private void enqueueMessage(JsMessage message) {
|
||||
synchronized (this) {
|
||||
if (activeBridgeMode == null) {
|
||||
LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
|
||||
return;
|
||||
}
|
||||
queue.add(message);
|
||||
if (!paused) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaused(boolean value) {
|
||||
if (paused && value) {
|
||||
// This should never happen. If a use-case for it comes up, we should
|
||||
// change pause to be a counter.
|
||||
LOG.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
|
||||
}
|
||||
paused = value;
|
||||
if (!value) {
|
||||
synchronized (this) {
|
||||
if (!queue.isEmpty() && activeBridgeMode != null) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class BridgeMode {
|
||||
public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
|
||||
public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
|
||||
public void reset() {}
|
||||
}
|
||||
|
||||
/** Uses JS polls for messages on a timer.. */
|
||||
public static class NoOpBridgeMode extends BridgeMode {
|
||||
@Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||
private final CordovaWebViewEngine engine;
|
||||
private final CordovaInterface cordova;
|
||||
|
||||
public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||
this.engine = engine;
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
engine.loadUrl("javascript:" + js, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses online/offline events to tell the JS when to poll for messages. */
|
||||
public static class OnlineEventsBridgeMode extends BridgeMode {
|
||||
private final OnlineEventsBridgeModeDelegate delegate;
|
||||
private boolean online;
|
||||
private boolean ignoreNextFlush;
|
||||
|
||||
public interface OnlineEventsBridgeModeDelegate {
|
||||
void setNetworkAvailable(boolean value);
|
||||
void runOnUiThread(Runnable r);
|
||||
}
|
||||
|
||||
public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
online = false;
|
||||
// If the following call triggers a notifyOfFlush, then ignore it.
|
||||
ignoreNextFlush = true;
|
||||
delegate.setNetworkAvailable(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
delegate.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (!queue.isEmpty()) {
|
||||
ignoreNextFlush = false;
|
||||
delegate.setNetworkAvailable(online);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Track when online/offline events are fired so that we don't fire excess events.
|
||||
@Override
|
||||
public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) {
|
||||
if (fromOnlineEvent && !ignoreNextFlush) {
|
||||
online = !online;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses webView.evaluateJavascript to execute messages. */
|
||||
public static class EvalBridgeMode extends BridgeMode {
|
||||
private final CordovaWebViewEngine engine;
|
||||
private final CordovaInterface cordova;
|
||||
|
||||
public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||
this.engine = engine;
|
||||
this.cordova = cordova;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
|
||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
String js = queue.popAndEncodeAsJs();
|
||||
if (js != null) {
|
||||
engine.evaluateJavascript(js, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class JsMessage {
|
||||
final String jsPayloadOrCallbackId;
|
||||
final PluginResult pluginResult;
|
||||
JsMessage(String js) {
|
||||
if (js == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
jsPayloadOrCallbackId = js;
|
||||
pluginResult = null;
|
||||
}
|
||||
JsMessage(PluginResult pluginResult, String callbackId) {
|
||||
if (callbackId == null || pluginResult == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
jsPayloadOrCallbackId = callbackId;
|
||||
this.pluginResult = pluginResult;
|
||||
}
|
||||
|
||||
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
|
||||
switch (pluginResult.getMessageType()) {
|
||||
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
|
||||
case PluginResult.MESSAGE_TYPE_NULL: // N
|
||||
return 1;
|
||||
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
||||
return 1 + pluginResult.getMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_STRING: // s
|
||||
return 1 + pluginResult.getStrMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
||||
return 1 + pluginResult.getMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
||||
return 1 + pluginResult.getMessage().length();
|
||||
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||
int ret = 1;
|
||||
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
|
||||
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
|
||||
int argLength = String.valueOf(length).length();
|
||||
ret += argLength + 1 + length;
|
||||
}
|
||||
return ret;
|
||||
case PluginResult.MESSAGE_TYPE_JSON:
|
||||
default:
|
||||
return pluginResult.getMessage().length();
|
||||
}
|
||||
}
|
||||
|
||||
int calculateEncodedLength() {
|
||||
if (pluginResult == null) {
|
||||
return jsPayloadOrCallbackId.length() + 1;
|
||||
}
|
||||
int statusLen = String.valueOf(pluginResult.getStatus()).length();
|
||||
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
|
||||
return ret + calculateEncodedLengthHelper(pluginResult);
|
||||
}
|
||||
|
||||
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
|
||||
switch (pluginResult.getMessageType()) {
|
||||
case PluginResult.MESSAGE_TYPE_BOOLEAN:
|
||||
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_NULL: // N
|
||||
sb.append('N');
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_NUMBER: // n
|
||||
sb.append('n')
|
||||
.append(pluginResult.getMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_STRING: // s
|
||||
sb.append('s');
|
||||
sb.append(pluginResult.getStrMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
|
||||
sb.append('S');
|
||||
sb.append(pluginResult.getMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
|
||||
sb.append('A');
|
||||
sb.append(pluginResult.getMessage());
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||
sb.append('M');
|
||||
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
|
||||
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
|
||||
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
|
||||
sb.append(' ');
|
||||
encodeAsMessageHelper(sb, multipartMessage);
|
||||
}
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_JSON:
|
||||
default:
|
||||
sb.append(pluginResult.getMessage()); // [ or {
|
||||
}
|
||||
}
|
||||
|
||||
void encodeAsMessage(StringBuilder sb) {
|
||||
if (pluginResult == null) {
|
||||
sb.append('J')
|
||||
.append(jsPayloadOrCallbackId);
|
||||
return;
|
||||
}
|
||||
int status = pluginResult.getStatus();
|
||||
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
|
||||
boolean resultOk = status == PluginResult.Status.OK.ordinal();
|
||||
boolean keepCallback = pluginResult.getKeepCallback();
|
||||
|
||||
sb.append((noResult || resultOk) ? 'S' : 'F')
|
||||
.append(keepCallback ? '1' : '0')
|
||||
.append(status)
|
||||
.append(' ')
|
||||
.append(jsPayloadOrCallbackId)
|
||||
.append(' ');
|
||||
|
||||
encodeAsMessageHelper(sb, pluginResult);
|
||||
}
|
||||
|
||||
void buildJsMessage(StringBuilder sb) {
|
||||
switch (pluginResult.getMessageType()) {
|
||||
case PluginResult.MESSAGE_TYPE_MULTIPART:
|
||||
int size = pluginResult.getMultipartMessagesSize();
|
||||
for (int i=0; i<size; i++) {
|
||||
PluginResult subresult = pluginResult.getMultipartMessage(i);
|
||||
JsMessage submessage = new JsMessage(subresult, jsPayloadOrCallbackId);
|
||||
submessage.buildJsMessage(sb);
|
||||
if (i < (size-1)) {
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
|
||||
sb.append("atob('")
|
||||
.append(pluginResult.getMessage())
|
||||
.append("')");
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
|
||||
sb.append("cordova.require('cordova/base64').toArrayBuffer('")
|
||||
.append(pluginResult.getMessage())
|
||||
.append("')");
|
||||
break;
|
||||
case PluginResult.MESSAGE_TYPE_NULL:
|
||||
sb.append("null");
|
||||
break;
|
||||
default:
|
||||
sb.append(pluginResult.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void encodeAsJsMessage(StringBuilder sb) {
|
||||
if (pluginResult == null) {
|
||||
sb.append(jsPayloadOrCallbackId);
|
||||
} else {
|
||||
int status = pluginResult.getStatus();
|
||||
boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
|
||||
sb.append("cordova.callbackFromNative('")
|
||||
.append(jsPayloadOrCallbackId)
|
||||
.append("',")
|
||||
.append(success)
|
||||
.append(",")
|
||||
.append(status)
|
||||
.append(",[");
|
||||
buildJsMessage(sb);
|
||||
sb.append("],")
|
||||
.append(pluginResult.getKeepCallback())
|
||||
.append(");");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
/**
|
||||
* This class provides reflective methods for permission requesting and checking so that plugins
|
||||
* written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
|
||||
*/
|
||||
public class PermissionHelper {
|
||||
private static final String LOG_TAG = "CordovaPermissionHelper";
|
||||
|
||||
/**
|
||||
* Requests a "dangerous" permission for the application at runtime. This is a helper method
|
||||
* alternative to cordovaInterface.requestPermission() that does not require the project to be
|
||||
* built with cordova-android 5.0.0+
|
||||
*
|
||||
* @param plugin The plugin the permission is being requested for
|
||||
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
|
||||
* along with the result of the permission request
|
||||
* @param permission The permission to be requested
|
||||
*/
|
||||
public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
|
||||
PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests "dangerous" permissions for the application at runtime. This is a helper method
|
||||
* alternative to cordovaInterface.requestPermissions() that does not require the project to be
|
||||
* built with cordova-android 5.0.0+
|
||||
*
|
||||
* @param plugin The plugin the permissions are being requested for
|
||||
* @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult()
|
||||
* along with the result of the permissions request
|
||||
* @param permissions The permissions to be requested
|
||||
*/
|
||||
public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
|
||||
plugin.cordova.requestPermissions(plugin, requestCode, permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks at runtime to see if the application has been granted a permission. This is a helper
|
||||
* method alternative to cordovaInterface.hasPermission() that does not require the project to
|
||||
* be built with cordova-android 5.0.0+
|
||||
*
|
||||
* @param plugin The plugin the permission is being checked against
|
||||
* @param permission The permission to be checked
|
||||
*
|
||||
* @return True if the permission has already been granted and false otherwise
|
||||
*/
|
||||
public static boolean hasPermission(CordovaPlugin plugin, String permission) {
|
||||
return plugin.cordova.hasPermission(permission);
|
||||
}
|
||||
|
||||
private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
|
||||
// Generate the request results
|
||||
int[] requestResults = new int[permissions.length];
|
||||
Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
try {
|
||||
plugin.onRequestPermissionResult(requestCode, permissions, requestResults);
|
||||
} catch (JSONException e) {
|
||||
LOG.e(LOG_TAG, "JSONException when delivering permissions results", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
|
||||
/**
|
||||
* This class represents a service entry object.
|
||||
*/
|
||||
public final class PluginEntry {
|
||||
|
||||
/**
|
||||
* The name of the service that this plugin implements
|
||||
*/
|
||||
public final String service;
|
||||
|
||||
/**
|
||||
* The plugin class name that implements the service.
|
||||
*/
|
||||
public final String pluginClass;
|
||||
|
||||
/**
|
||||
* The pre-instantiated plugin to use for this entry.
|
||||
*/
|
||||
public final CordovaPlugin plugin;
|
||||
|
||||
/**
|
||||
* Flag that indicates the plugin object should be created when PluginManager is initialized.
|
||||
*/
|
||||
public final boolean onload;
|
||||
|
||||
/**
|
||||
* Constructs with a CordovaPlugin already instantiated.
|
||||
*/
|
||||
public PluginEntry(String service, CordovaPlugin plugin) {
|
||||
this(service, plugin.getClass().getName(), true, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param service The name of the service
|
||||
* @param pluginClass The plugin class name
|
||||
* @param onload Create plugin object when HTML page is loaded
|
||||
*/
|
||||
public PluginEntry(String service, String pluginClass, boolean onload) {
|
||||
this(service, pluginClass, onload, null);
|
||||
}
|
||||
|
||||
private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) {
|
||||
this.service = service;
|
||||
this.pluginClass = pluginClass;
|
||||
this.onload = onload;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,526 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Debug;
|
||||
|
||||
/**
|
||||
* PluginManager is exposed to JavaScript in the Cordova WebView.
|
||||
*
|
||||
* Calling native plugin code can be done by calling PluginManager.exec(...)
|
||||
* from JavaScript.
|
||||
*/
|
||||
public class PluginManager {
|
||||
private static String TAG = "PluginManager";
|
||||
private static final int SLOW_EXEC_WARNING_THRESHOLD = Debug.isDebuggerConnected() ? 60 : 16;
|
||||
|
||||
// List of service entries
|
||||
private final LinkedHashMap<String, CordovaPlugin> pluginMap = new LinkedHashMap<String, CordovaPlugin>();
|
||||
private final LinkedHashMap<String, PluginEntry> entryMap = new LinkedHashMap<String, PluginEntry>();
|
||||
|
||||
private final CordovaInterface ctx;
|
||||
private final CordovaWebView app;
|
||||
private boolean isInitialized;
|
||||
|
||||
private CordovaPlugin permissionRequester;
|
||||
|
||||
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
|
||||
this.ctx = cordova;
|
||||
this.app = cordovaWebView;
|
||||
setPluginEntries(pluginEntries);
|
||||
}
|
||||
|
||||
public Collection<PluginEntry> getPluginEntries() {
|
||||
return entryMap.values();
|
||||
}
|
||||
|
||||
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
|
||||
if (isInitialized) {
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
entryMap.clear();
|
||||
}
|
||||
for (PluginEntry entry : pluginEntries) {
|
||||
addService(entry);
|
||||
}
|
||||
if (isInitialized) {
|
||||
startupPlugins();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init when loading a new HTML page into webview.
|
||||
*/
|
||||
public void init() {
|
||||
LOG.d(TAG, "init()");
|
||||
isInitialized = true;
|
||||
this.onPause(false);
|
||||
this.onDestroy();
|
||||
pluginMap.clear();
|
||||
this.startupPlugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create plugins objects that have onload set.
|
||||
*/
|
||||
private void startupPlugins() {
|
||||
for (PluginEntry entry : entryMap.values()) {
|
||||
// Add a null entry to for each non-startup plugin to avoid ConcurrentModificationException
|
||||
// When iterating plugins.
|
||||
if (entry.onload) {
|
||||
getPlugin(entry.service);
|
||||
} else {
|
||||
pluginMap.put(entry.service, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a request for execution and fulfills it by finding the appropriate
|
||||
* Java class and calling it's execute method.
|
||||
*
|
||||
* PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
|
||||
* string is returned that will indicate if any errors have occurred when trying to find
|
||||
* or execute the class denoted by the clazz argument.
|
||||
*
|
||||
* @param service String containing the service to run
|
||||
* @param action String containing the action that the class is supposed to perform. This is
|
||||
* passed to the plugin execute method and it is up to the plugin developer
|
||||
* how to deal with it.
|
||||
* @param callbackId String containing the id of the callback that is execute in JavaScript if
|
||||
* this is an async plugin call.
|
||||
* @param rawArgs An Array literal string containing any arguments needed in the
|
||||
* plugin execute method.
|
||||
*/
|
||||
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
|
||||
CordovaPlugin plugin = getPlugin(service);
|
||||
if (plugin == null) {
|
||||
LOG.d(TAG, "exec() call to unknown plugin: " + service);
|
||||
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
|
||||
app.sendPluginResult(cr, callbackId);
|
||||
return;
|
||||
}
|
||||
CallbackContext callbackContext = new CallbackContext(callbackId, app);
|
||||
try {
|
||||
long pluginStartTime = System.currentTimeMillis();
|
||||
boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
|
||||
long duration = System.currentTimeMillis() - pluginStartTime;
|
||||
|
||||
if (duration > SLOW_EXEC_WARNING_THRESHOLD) {
|
||||
LOG.w(TAG, "THREAD WARNING: exec() call to " + service + "." + action + " blocked the main thread for " + duration + "ms. Plugin should use CordovaInterface.getThreadPool().");
|
||||
}
|
||||
if (!wasValidAction) {
|
||||
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
|
||||
callbackContext.sendPluginResult(cr);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
|
||||
callbackContext.sendPluginResult(cr);
|
||||
} catch (Exception e) {
|
||||
LOG.e(TAG, "Uncaught exception from plugin", e);
|
||||
callbackContext.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin object that implements the service.
|
||||
* If the plugin object does not already exist, then create it.
|
||||
* If the service doesn't exist, then return null.
|
||||
*
|
||||
* @param service The name of the service.
|
||||
* @return CordovaPlugin or null
|
||||
*/
|
||||
public CordovaPlugin getPlugin(String service) {
|
||||
CordovaPlugin ret = pluginMap.get(service);
|
||||
if (ret == null) {
|
||||
PluginEntry pe = entryMap.get(service);
|
||||
if (pe == null) {
|
||||
return null;
|
||||
}
|
||||
if (pe.plugin != null) {
|
||||
ret = pe.plugin;
|
||||
} else {
|
||||
ret = instantiatePlugin(pe.pluginClass);
|
||||
}
|
||||
ret.privateInitialize(service, ctx, app, app.getPreferences());
|
||||
pluginMap.put(service, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plugin class that implements a service to the service entry table.
|
||||
* This does not create the plugin object instance.
|
||||
*
|
||||
* @param service The service name
|
||||
* @param className The plugin class name
|
||||
*/
|
||||
public void addService(String service, String className) {
|
||||
PluginEntry entry = new PluginEntry(service, className, false);
|
||||
this.addService(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plugin class that implements a service to the service entry table.
|
||||
* This does not create the plugin object instance.
|
||||
*
|
||||
* @param entry The plugin entry
|
||||
*/
|
||||
public void addService(PluginEntry entry) {
|
||||
this.entryMap.put(entry.service, entry);
|
||||
if (entry.plugin != null) {
|
||||
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
|
||||
pluginMap.put(entry.service, entry.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity.
|
||||
*
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onPause(boolean multitasking) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onPause(multitasking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system received an HTTP authentication request. Plugins can use
|
||||
* the supplied HttpAuthHandler to process this auth challenge.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param handler The HttpAuthHandler used to set the WebView's response
|
||||
* @param host The host requiring authentication
|
||||
* @param realm The realm for which authentication is required
|
||||
*
|
||||
* @return Returns True if there is a plugin which will resolve this auth challenge, otherwise False
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when he system received an SSL client certificate request. Plugin can use
|
||||
* the supplied ClientCertRequest to process this certificate challenge.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param request The client certificate request
|
||||
*
|
||||
* @return Returns True if plugin will resolve this auth challenge, otherwise False
|
||||
*
|
||||
*/
|
||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user.
|
||||
*
|
||||
* @param multitasking Flag indicating if multitasking is turned on for app
|
||||
*/
|
||||
public void onResume(boolean multitasking) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onResume(multitasking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is becoming visible to the user.
|
||||
*/
|
||||
public void onStart() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity is no longer visible to the user.
|
||||
*/
|
||||
public void onStop() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The final call you receive before your activity is destroyed.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all plugins.
|
||||
*
|
||||
* @param id The message id
|
||||
* @param data The message data
|
||||
* @return Object to stop propagation or null
|
||||
*/
|
||||
public Object postMessage(String id, Object data) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Object obj = plugin.onMessage(id, data);
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.onMessage(id, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity receives a new intent.
|
||||
*/
|
||||
public void onNewIntent(Intent intent) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to request an external resource.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the resource to load,
|
||||
* false to block the resource.
|
||||
*/
|
||||
public boolean shouldAllowRequest(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowRequest(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {
|
||||
return true;
|
||||
}
|
||||
// TalkBack requires this, so allow it by default.
|
||||
if (url.startsWith("https://ssl.gstatic.com/accessibility/javascript/android/")) {
|
||||
return true;
|
||||
}
|
||||
if (url.startsWith("file://")) {
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return !url.contains("/app_webview/");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going to change the URL of the loaded content.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the navigation,
|
||||
* false to block the navigation.
|
||||
*/
|
||||
public boolean shouldAllowNavigation(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowNavigation(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
return url.startsWith("file://") || url.startsWith("about:blank");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the webview is requesting the exec() bridge be enabled.
|
||||
*/
|
||||
public boolean shouldAllowBridgeAccess(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldAllowBridgeAccess(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default policy:
|
||||
return url.startsWith("file://");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the webview is going not going to navigate, but may launch
|
||||
* an Intent for an URL.
|
||||
*
|
||||
* This delegates to the installed plugins, and returns true/false for the
|
||||
* first plugin to provide a non-null result. If no plugins respond, then
|
||||
* the default policy is applied.
|
||||
*
|
||||
* @param url The URL that is being requested.
|
||||
* @return Returns true to allow the URL to launch an intent,
|
||||
* false to block the intent.
|
||||
*/
|
||||
public Boolean shouldOpenExternalUrl(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null) {
|
||||
Boolean result = plugin.shouldOpenExternalUrl(url);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default policy:
|
||||
// External URLs are not allowed
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the URL of the webview changes.
|
||||
*
|
||||
* @param url The URL that is being changed to.
|
||||
* @return Return false to allow the URL to load, return true to prevent the URL from loading.
|
||||
*/
|
||||
public boolean onOverrideUrlLoading(String url) {
|
||||
for (PluginEntry entry : this.entryMap.values()) {
|
||||
CordovaPlugin plugin = pluginMap.get(entry.service);
|
||||
if (plugin != null && plugin.onOverrideUrlLoading(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the app navigates or refreshes.
|
||||
*/
|
||||
public void onReset() {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uri remapUri(Uri uri) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Uri ret = plugin.remapUri(uri);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plugin based on class name.
|
||||
*/
|
||||
private CordovaPlugin instantiatePlugin(String className) {
|
||||
CordovaPlugin ret = null;
|
||||
try {
|
||||
Class<?> c = null;
|
||||
if ((className != null) && !("".equals(className))) {
|
||||
c = Class.forName(className);
|
||||
}
|
||||
if (c != null & CordovaPlugin.class.isAssignableFrom(c)) {
|
||||
ret = (CordovaPlugin) c.newInstance();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Error adding plugin " + className + ".");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system when the device configuration changes while your activity is running.
|
||||
*
|
||||
* @param newConfig The new device configuration
|
||||
*/
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
plugin.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bundle onSaveInstanceState() {
|
||||
Bundle state = new Bundle();
|
||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||
if (plugin != null) {
|
||||
Bundle pluginState = plugin.onSaveInstanceState();
|
||||
if(pluginState != null) {
|
||||
state.putBundle(plugin.getServiceName(), pluginState);
|
||||
}
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
public class PluginResult {
|
||||
private final int status;
|
||||
private final int messageType;
|
||||
private boolean keepCallback = false;
|
||||
private String strMessage;
|
||||
private String encodedMessage;
|
||||
private List<PluginResult> multipartMessages;
|
||||
|
||||
public PluginResult(Status status) {
|
||||
this(status, PluginResult.StatusMessages[status.ordinal()]);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, String message) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = message == null ? MESSAGE_TYPE_NULL : MESSAGE_TYPE_STRING;
|
||||
this.strMessage = message;
|
||||
}
|
||||
|
||||
public PluginResult(Status status, JSONArray message) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = MESSAGE_TYPE_JSON;
|
||||
encodedMessage = message.toString();
|
||||
}
|
||||
|
||||
public PluginResult(Status status, JSONObject message) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = MESSAGE_TYPE_JSON;
|
||||
encodedMessage = message.toString();
|
||||
}
|
||||
|
||||
public PluginResult(Status status, int i) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = MESSAGE_TYPE_NUMBER;
|
||||
this.encodedMessage = ""+i;
|
||||
}
|
||||
|
||||
public PluginResult(Status status, float f) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = MESSAGE_TYPE_NUMBER;
|
||||
this.encodedMessage = ""+f;
|
||||
}
|
||||
|
||||
public PluginResult(Status status, boolean b) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = MESSAGE_TYPE_BOOLEAN;
|
||||
this.encodedMessage = Boolean.toString(b);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, byte[] data) {
|
||||
this(status, data, false);
|
||||
}
|
||||
|
||||
public PluginResult(Status status, byte[] data, boolean binaryString) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
|
||||
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
// The keepCallback and status of multipartMessages are ignored.
|
||||
public PluginResult(Status status, List<PluginResult> multipartMessages) {
|
||||
this.status = status.ordinal();
|
||||
this.messageType = MESSAGE_TYPE_MULTIPART;
|
||||
this.multipartMessages = multipartMessages;
|
||||
}
|
||||
|
||||
public void setKeepCallback(boolean b) {
|
||||
this.keepCallback = b;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public int getMessageType() {
|
||||
return messageType;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
if (encodedMessage == null) {
|
||||
encodedMessage = JSONObject.quote(strMessage);
|
||||
}
|
||||
return encodedMessage;
|
||||
}
|
||||
|
||||
public int getMultipartMessagesSize() {
|
||||
return multipartMessages.size();
|
||||
}
|
||||
|
||||
public PluginResult getMultipartMessage(int index) {
|
||||
return multipartMessages.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
|
||||
* Otherwise, returns null.
|
||||
*/
|
||||
public String getStrMessage() {
|
||||
return strMessage;
|
||||
}
|
||||
|
||||
public boolean getKeepCallback() {
|
||||
return this.keepCallback;
|
||||
}
|
||||
|
||||
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||
public String getJSONString() {
|
||||
return "{\"status\":" + this.status + ",\"message\":" + this.getMessage() + ",\"keepCallback\":" + this.keepCallback + "}";
|
||||
}
|
||||
|
||||
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||
public String toCallbackString(String callbackId) {
|
||||
// If no result to be sent and keeping callback, then no need to sent back to JavaScript
|
||||
if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
|
||||
if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
|
||||
return toSuccessCallbackString(callbackId);
|
||||
}
|
||||
|
||||
return toErrorCallbackString(callbackId);
|
||||
}
|
||||
|
||||
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||
public String toSuccessCallbackString(String callbackId) {
|
||||
return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");";
|
||||
}
|
||||
|
||||
@Deprecated // Use sendPluginResult instead of sendJavascript.
|
||||
public String toErrorCallbackString(String callbackId) {
|
||||
return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");";
|
||||
}
|
||||
|
||||
public static final int MESSAGE_TYPE_STRING = 1;
|
||||
public static final int MESSAGE_TYPE_JSON = 2;
|
||||
public static final int MESSAGE_TYPE_NUMBER = 3;
|
||||
public static final int MESSAGE_TYPE_BOOLEAN = 4;
|
||||
public static final int MESSAGE_TYPE_NULL = 5;
|
||||
public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
|
||||
// Use BINARYSTRING when your string may contain null characters.
|
||||
// This is required to work around a bug in the platform :(.
|
||||
public static final int MESSAGE_TYPE_BINARYSTRING = 7;
|
||||
public static final int MESSAGE_TYPE_MULTIPART = 8;
|
||||
|
||||
public static String[] StatusMessages = new String[] {
|
||||
"No result",
|
||||
"OK",
|
||||
"Class not found",
|
||||
"Illegal access",
|
||||
"Instantiation error",
|
||||
"Malformed url",
|
||||
"IO error",
|
||||
"Invalid action",
|
||||
"JSON error",
|
||||
"Error"
|
||||
};
|
||||
|
||||
public enum Status {
|
||||
NO_RESULT,
|
||||
OK,
|
||||
CLASS_NOT_FOUND_EXCEPTION,
|
||||
ILLEGAL_ACCESS_EXCEPTION,
|
||||
INSTANTIATION_EXCEPTION,
|
||||
MALFORMED_URL_EXCEPTION,
|
||||
IO_EXCEPTION,
|
||||
INVALID_ACTION,
|
||||
JSON_EXCEPTION,
|
||||
ERROR
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ResumeCallback extends CallbackContext {
|
||||
private final String TAG = "CordovaResumeCallback";
|
||||
private String serviceName;
|
||||
private PluginManager pluginManager;
|
||||
|
||||
public ResumeCallback(String serviceName, PluginManager pluginManager) {
|
||||
super("resumecallback", null);
|
||||
this.serviceName = serviceName;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginResult(PluginResult pluginResult) {
|
||||
synchronized (this) {
|
||||
if (finished) {
|
||||
LOG.w(TAG, serviceName + " attempted to send a second callback to ResumeCallback\nResult was: " + pluginResult.getMessage());
|
||||
return;
|
||||
} else {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
JSONObject pluginResultObject = new JSONObject();
|
||||
|
||||
try {
|
||||
pluginResultObject.put("pluginServiceName", this.serviceName);
|
||||
pluginResultObject.put("pluginStatus", PluginResult.StatusMessages[pluginResult.getStatus()]);
|
||||
|
||||
event.put("action", "resume");
|
||||
event.put("pendingResult", pluginResultObject);
|
||||
} catch (JSONException e) {
|
||||
LOG.e(TAG, "Unable to create resume object for Activity Result");
|
||||
}
|
||||
|
||||
PluginResult eventResult = new PluginResult(PluginResult.Status.OK, event);
|
||||
|
||||
// We send a list of results to the js so that we don't have to decode
|
||||
// the PluginResult passed to this CallbackContext into JSON twice.
|
||||
// The results are combined into an event payload before the event is
|
||||
// fired on the js side of things (see platform.js)
|
||||
List<PluginResult> result = new ArrayList<PluginResult>();
|
||||
result.add(eventResult);
|
||||
result.add(pluginResult);
|
||||
|
||||
CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||
appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, result));
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
public class Whitelist {
|
||||
private static class URLPattern {
|
||||
public Pattern scheme;
|
||||
public Pattern host;
|
||||
public Integer port;
|
||||
public Pattern path;
|
||||
|
||||
private String regexFromPattern(String pattern, boolean allowWildcards) {
|
||||
final String toReplace = "\\.[]{}()^$?+|";
|
||||
StringBuilder regex = new StringBuilder();
|
||||
for (int i=0; i < pattern.length(); i++) {
|
||||
char c = pattern.charAt(i);
|
||||
if (c == '*' && allowWildcards) {
|
||||
regex.append(".");
|
||||
} else if (toReplace.indexOf(c) > -1) {
|
||||
regex.append('\\');
|
||||
}
|
||||
regex.append(c);
|
||||
}
|
||||
return regex.toString();
|
||||
}
|
||||
|
||||
public URLPattern(String scheme, String host, String port, String path) throws MalformedURLException {
|
||||
try {
|
||||
if (scheme == null || "*".equals(scheme)) {
|
||||
this.scheme = null;
|
||||
} else {
|
||||
this.scheme = Pattern.compile(regexFromPattern(scheme, false), Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
if ("*".equals(host)) {
|
||||
this.host = null;
|
||||
} else if (host.startsWith("*.")) {
|
||||
this.host = Pattern.compile("([a-z0-9.-]*\\.)?" + regexFromPattern(host.substring(2), false), Pattern.CASE_INSENSITIVE);
|
||||
} else {
|
||||
this.host = Pattern.compile(regexFromPattern(host, false), Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
if (port == null || "*".equals(port)) {
|
||||
this.port = null;
|
||||
} else {
|
||||
this.port = Integer.parseInt(port,10);
|
||||
}
|
||||
if (path == null || "/*".equals(path)) {
|
||||
this.path = null;
|
||||
} else {
|
||||
this.path = Pattern.compile(regexFromPattern(path, true));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new MalformedURLException("Port must be a number");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(Uri uri) {
|
||||
try {
|
||||
return ((scheme == null || scheme.matcher(uri.getScheme()).matches()) &&
|
||||
(host == null || host.matcher(uri.getHost()).matches()) &&
|
||||
(port == null || port.equals(uri.getPort())) &&
|
||||
(path == null || path.matcher(uri.getPath()).matches()));
|
||||
} catch (Exception e) {
|
||||
LOG.d(TAG, e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<URLPattern> whiteList;
|
||||
|
||||
public static final String TAG = "Whitelist";
|
||||
|
||||
public Whitelist() {
|
||||
this.whiteList = new ArrayList<URLPattern>();
|
||||
}
|
||||
|
||||
/* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)
|
||||
*
|
||||
* <url-pattern> := <scheme>://<host><path>
|
||||
* <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome-extension'
|
||||
* <host> := '*' | '*.' <any char except '/' and '*'>+
|
||||
* <path> := '/' <any chars>
|
||||
*
|
||||
* We extend this to explicitly allow a port attached to the host, and we allow
|
||||
* the scheme to be omitted for backwards compatibility. (Also host is not required
|
||||
* to begin with a "*" or "*.".)
|
||||
*/
|
||||
public void addWhiteListEntry(String origin, boolean subdomains) {
|
||||
if (whiteList != null) {
|
||||
try {
|
||||
// Unlimited access to network resources
|
||||
if (origin.compareTo("*") == 0) {
|
||||
LOG.d(TAG, "Unlimited access to network resources");
|
||||
whiteList = null;
|
||||
}
|
||||
else { // specific access
|
||||
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+):(//)?)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
|
||||
Matcher m = parts.matcher(origin);
|
||||
if (m.matches()) {
|
||||
String scheme = m.group(2);
|
||||
String host = m.group(4);
|
||||
// Special case for two urls which are allowed to have empty hosts
|
||||
if (("file".equals(scheme) || "content".equals(scheme)) && host == null) host = "*";
|
||||
String port = m.group(8);
|
||||
String path = m.group(9);
|
||||
if (scheme == null) {
|
||||
// XXX making it stupid friendly for people who forget to include protocol/SSL
|
||||
whiteList.add(new URLPattern("http", host, port, path));
|
||||
whiteList.add(new URLPattern("https", host, port, path));
|
||||
} else {
|
||||
whiteList.add(new URLPattern(scheme, host, port, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.d(TAG, "Failed to add origin %s", origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if URL is in approved list of URLs to load.
|
||||
*
|
||||
* @param uri
|
||||
* @return true if wide open or whitelisted
|
||||
*/
|
||||
public boolean isUrlWhiteListed(String uri) {
|
||||
// If there is no whitelist, then it's wide open
|
||||
if (whiteList == null) return true;
|
||||
|
||||
Uri parsedUri = Uri.parse(uri);
|
||||
// Look for match in white list
|
||||
Iterator<URLPattern> pit = whiteList.iterator();
|
||||
while (pit.hasNext()) {
|
||||
URLPattern p = pit.next();
|
||||
if (p.matches(parsedUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.apache.cordova.ICordovaCookieManager;
|
||||
|
||||
class SystemCookieManager implements ICordovaCookieManager {
|
||||
|
||||
protected final WebView webView;
|
||||
private final CookieManager cookieManager;
|
||||
|
||||
//Added because lint can't see the conditional RIGHT ABOVE this
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public SystemCookieManager(WebView webview) {
|
||||
webView = webview;
|
||||
cookieManager = CookieManager.getInstance();
|
||||
|
||||
//REALLY? Nobody has seen this UNTIL NOW?
|
||||
cookieManager.setAcceptFileSchemeCookies(true);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.setAcceptThirdPartyCookies(webView, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCookiesEnabled(boolean accept) {
|
||||
cookieManager.setAcceptCookie(accept);
|
||||
}
|
||||
|
||||
public void setCookie(final String url, final String value) {
|
||||
cookieManager.setCookie(url, value);
|
||||
}
|
||||
|
||||
public String getCookie(final String url) {
|
||||
return cookieManager.getCookie(url);
|
||||
}
|
||||
|
||||
public void clearCookies() {
|
||||
cookieManager.removeAllCookie();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.flush();
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.webkit.JavascriptInterface;
|
||||
|
||||
import org.apache.cordova.CordovaBridge;
|
||||
import org.apache.cordova.ExposedJsApi;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
class SystemExposedJsApi implements ExposedJsApi {
|
||||
private final CordovaBridge bridge;
|
||||
|
||||
SystemExposedJsApi(CordovaBridge bridge) {
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
|
||||
}
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import java.util.Arrays;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.GeolocationPermissions.Callback;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebStorage;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.apache.cordova.CordovaDialogsHelper;
|
||||
import org.apache.cordova.CordovaPlugin;
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
/**
|
||||
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||
* The kind of callbacks that happen here are on the chrome outside the document,
|
||||
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
||||
* to but different than CordovaWebViewClient.
|
||||
*/
|
||||
public class SystemWebChromeClient extends WebChromeClient {
|
||||
|
||||
private static final int FILECHOOSER_RESULTCODE = 5173;
|
||||
private static final String LOG_TAG = "SystemWebChromeClient";
|
||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||
protected final SystemWebViewEngine parentEngine;
|
||||
|
||||
// the video progress view
|
||||
private View mVideoProgressView;
|
||||
|
||||
private CordovaDialogsHelper dialogsHelper;
|
||||
private Context appContext;
|
||||
|
||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
private View mCustomView;
|
||||
|
||||
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
|
||||
this.parentEngine = parentEngine;
|
||||
appContext = parentEngine.webView.getContext();
|
||||
dialogsHelper = new CordovaDialogsHelper(appContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a javascript alert dialog.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
|
||||
@Override public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm();
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a confirm dialog to the user.
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
|
||||
@Override
|
||||
public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm();
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to display a prompt dialog to the user.
|
||||
* If the client returns true, WebView will assume that the client will
|
||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||
*
|
||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||
* this purpose, perhaps we should hack console.log to do this instead!
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
|
||||
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
|
||||
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||
if (handledRet != null) {
|
||||
result.confirm(handledRet);
|
||||
} else {
|
||||
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
|
||||
@Override
|
||||
public void gotResult(boolean success, String value) {
|
||||
if (success) {
|
||||
result.confirm(value);
|
||||
} else {
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle database quota exceeded notification.
|
||||
*/
|
||||
@Override
|
||||
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
||||
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
||||
{
|
||||
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
||||
{
|
||||
if (consoleMessage.message() != null)
|
||||
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||
return super.onConsoleMessage(consoleMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
|
||||
*
|
||||
* This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
|
||||
*
|
||||
* @param origin
|
||||
* @param callback
|
||||
*/
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
|
||||
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
||||
callback.invoke(origin, true, false);
|
||||
//Get the plugin, it should be loaded
|
||||
CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
|
||||
if(geolocation != null && !geolocation.hasPermisssion())
|
||||
{
|
||||
geolocation.requestPermissions(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// API level 7 is required for this, see if we could lower this using something else
|
||||
@Override
|
||||
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||
parentEngine.getCordovaWebView().showCustomView(view, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
parentEngine.getCordovaWebView().hideCustomView();
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Ask the host application for a custom progress view to show while
|
||||
* a <video> is loading.
|
||||
* @return View The progress view.
|
||||
*/
|
||||
public View getVideoLoadingProgressView() {
|
||||
|
||||
if (mVideoProgressView == null) {
|
||||
// Create a new Loading view programmatically.
|
||||
|
||||
// create the linear layout
|
||||
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||
layout.setLayoutParams(layoutParams);
|
||||
// the proress bar
|
||||
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
|
||||
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
barLayoutParams.gravity = Gravity.CENTER;
|
||||
bar.setLayoutParams(barLayoutParams);
|
||||
layout.addView(bar);
|
||||
|
||||
mVideoProgressView = layout;
|
||||
}
|
||||
return mVideoProgressView;
|
||||
}
|
||||
|
||||
// <input type=file> support:
|
||||
// openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat).
|
||||
// For Lollipop, we use onShowFileChooser().
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||
this.openFileChooser(uploadMsg, "*/*");
|
||||
}
|
||||
|
||||
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
||||
this.openFileChooser(uploadMsg, acceptType, null);
|
||||
}
|
||||
|
||||
public void openFileChooser(final ValueCallback<Uri> uploadMsg, String acceptType, String capture)
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
|
||||
uploadMsg.onReceiveValue(result);
|
||||
}
|
||||
}, intent, FILECHOOSER_RESULTCODE);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
|
||||
Intent intent = fileChooserParams.createIntent();
|
||||
try {
|
||||
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
|
||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
|
||||
filePathsCallback.onReceiveValue(result);
|
||||
}
|
||||
}, intent, FILECHOOSER_RESULTCODE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
LOG.w("No activity found to handle file chooser intent.", e);
|
||||
filePathsCallback.onReceiveValue(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onPermissionRequest(final PermissionRequest request) {
|
||||
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
|
||||
request.grant(request.getResources());
|
||||
}
|
||||
|
||||
public void destroyLastDialog(){
|
||||
dialogsHelper.destroyLastDialog();
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
|
||||
/**
|
||||
* Custom WebView subclass that enables us to capture events needed for Cordova.
|
||||
*/
|
||||
public class SystemWebView extends WebView implements CordovaWebViewEngine.EngineView {
|
||||
private SystemWebViewClient viewClient;
|
||||
SystemWebChromeClient chromeClient;
|
||||
private SystemWebViewEngine parentEngine;
|
||||
private CordovaInterface cordova;
|
||||
|
||||
public SystemWebView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SystemWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
// Package visibility to enforce that only SystemWebViewEngine should call this method.
|
||||
void init(SystemWebViewEngine parentEngine, CordovaInterface cordova) {
|
||||
this.cordova = cordova;
|
||||
this.parentEngine = parentEngine;
|
||||
if (this.viewClient == null) {
|
||||
setWebViewClient(new SystemWebViewClient(parentEngine));
|
||||
}
|
||||
|
||||
if (this.chromeClient == null) {
|
||||
setWebChromeClient(new SystemWebChromeClient(parentEngine));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaWebView getCordovaWebView() {
|
||||
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebViewClient(WebViewClient client) {
|
||||
viewClient = (SystemWebViewClient)client;
|
||||
super.setWebViewClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebChromeClient(WebChromeClient client) {
|
||||
chromeClient = (SystemWebChromeClient)client;
|
||||
super.setWebChromeClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
|
||||
if (ret != null) {
|
||||
return ret.booleanValue();
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Build;
|
||||
import android.webkit.ClientCertRequest;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.apache.cordova.AuthenticationToken;
|
||||
import org.apache.cordova.CordovaClientCertRequest;
|
||||
import org.apache.cordova.CordovaHttpAuthHandler;
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.apache.cordova.LOG;
|
||||
import org.apache.cordova.PluginManager;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Hashtable;
|
||||
|
||||
|
||||
/**
|
||||
* This class is the WebViewClient that implements callbacks for our web view.
|
||||
* The kind of callbacks that happen here are regarding the rendering of the
|
||||
* document instead of the chrome surrounding it, such as onPageStarted(),
|
||||
* shouldOverrideUrlLoading(), etc. Related to but different than
|
||||
* CordovaChromeClient.
|
||||
*/
|
||||
public class SystemWebViewClient extends WebViewClient {
|
||||
|
||||
private static final String TAG = "SystemWebViewClient";
|
||||
protected final SystemWebViewEngine parentEngine;
|
||||
private boolean doClearHistory = false;
|
||||
boolean isCurrentlyLoading;
|
||||
|
||||
/** The authorization tokens. */
|
||||
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||
|
||||
public SystemWebViewClient(SystemWebViewEngine parentEngine) {
|
||||
this.parentEngine = parentEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the host application a chance to take over the control when a new url
|
||||
* is about to be loaded in the current WebView.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The url to be loaded.
|
||||
* @return true to override, false for default behavior
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return parentEngine.client.onNavigationAttempt(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* On received http auth request.
|
||||
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
|
||||
*/
|
||||
@Override
|
||||
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||
|
||||
// Get the authentication token (if specified)
|
||||
AuthenticationToken token = this.getAuthenticationToken(host, realm);
|
||||
if (token != null) {
|
||||
handler.proceed(token.getUserName(), token.getPassword());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is some plugin which can resolve this auth challenge
|
||||
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new CordovaHttpAuthHandler(handler), host, realm)) {
|
||||
parentEngine.client.clearLoadTimeoutTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// By default handle 401 like we'd normally do!
|
||||
super.onReceivedHttpAuthRequest(view, handler, host, realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* On received client cert request.
|
||||
* The method forwards the request to any running plugins before using the default implementation.
|
||||
*
|
||||
* @param view
|
||||
* @param request
|
||||
*/
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
|
||||
{
|
||||
|
||||
// Check if there is some plugin which can resolve this certificate request
|
||||
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new CordovaClientCertRequest(request))) {
|
||||
parentEngine.client.clearLoadTimeoutTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// By default pass to WebViewClient
|
||||
super.onReceivedClientCertRequest(view, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a page has started loading.
|
||||
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
|
||||
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
|
||||
* embedded frame changes, i.e. clicking a link whose target is an iframe.
|
||||
*
|
||||
* @param view The webview initiating the callback.
|
||||
* @param url The url of the page.
|
||||
*/
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
isCurrentlyLoading = true;
|
||||
// Flush stale messages & reset plugins.
|
||||
parentEngine.bridge.reset();
|
||||
parentEngine.client.onPageStarted(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a page has finished loading.
|
||||
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
|
||||
*
|
||||
*
|
||||
* @param view The webview initiating the callback.
|
||||
* @param url The url of the page.
|
||||
*/
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
// Ignore excessive calls, if url is not about:blank (CB-8317).
|
||||
if (!isCurrentlyLoading && !url.startsWith("about:")) {
|
||||
return;
|
||||
}
|
||||
isCurrentlyLoading = false;
|
||||
|
||||
/**
|
||||
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
||||
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
|
||||
* true. You see when you load a url with a # in it which is common in jQuery applications
|
||||
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
|
||||
*/
|
||||
if (this.doClearHistory) {
|
||||
view.clearHistory();
|
||||
this.doClearHistory = false;
|
||||
}
|
||||
parentEngine.client.onPageFinishedLoading(url);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
||||
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||
* @param description A String describing the error.
|
||||
* @param failingUrl The url that failed to load.
|
||||
*/
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
// Ignore error due to stopLoading().
|
||||
if (!isCurrentlyLoading) {
|
||||
return;
|
||||
}
|
||||
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||
|
||||
// If this is a "Protocol Not Supported" error, then revert to the previous
|
||||
// page. If there was no previous page, then punt. The application's config
|
||||
// is likely incorrect (start page set to sms: or something like that)
|
||||
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
|
||||
parentEngine.client.clearLoadTimeoutTimer();
|
||||
|
||||
if (view.canGoBack()) {
|
||||
view.goBack();
|
||||
return;
|
||||
} else {
|
||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||
}
|
||||
}
|
||||
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that an SSL error occurred while loading a resource.
|
||||
* The host application must call either handler.cancel() or handler.proceed().
|
||||
* Note that the decision may be retained for use in response to future SSL errors.
|
||||
* The default behavior is to cancel the load.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param handler An SslErrorHandler object that will handle the user's response.
|
||||
* @param error The SSL error object.
|
||||
*/
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||
|
||||
final String packageName = parentEngine.cordova.getActivity().getPackageName();
|
||||
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
// debug = true
|
||||
handler.proceed();
|
||||
return;
|
||||
} else {
|
||||
// debug = false
|
||||
super.onReceivedSslError(view, handler, error);
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
// When it doubt, lock it out!
|
||||
super.onReceivedSslError(view, handler, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the authentication token.
|
||||
*
|
||||
* @param authenticationToken
|
||||
* @param host
|
||||
* @param realm
|
||||
*/
|
||||
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
||||
if (host == null) {
|
||||
host = "";
|
||||
}
|
||||
if (realm == null) {
|
||||
realm = "";
|
||||
}
|
||||
this.authenticationTokens.put(host.concat(realm), authenticationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the authentication token.
|
||||
*
|
||||
* @param host
|
||||
* @param realm
|
||||
*
|
||||
* @return the authentication token or null if did not exist
|
||||
*/
|
||||
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
||||
return this.authenticationTokens.remove(host.concat(realm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authentication token.
|
||||
*
|
||||
* In order it tries:
|
||||
* 1- host + realm
|
||||
* 2- host
|
||||
* 3- realm
|
||||
* 4- no host, no realm
|
||||
*
|
||||
* @param host
|
||||
* @param realm
|
||||
*
|
||||
* @return the authentication token
|
||||
*/
|
||||
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
||||
AuthenticationToken token = null;
|
||||
token = this.authenticationTokens.get(host.concat(realm));
|
||||
|
||||
if (token == null) {
|
||||
// try with just the host
|
||||
token = this.authenticationTokens.get(host);
|
||||
|
||||
// Try the realm
|
||||
if (token == null) {
|
||||
token = this.authenticationTokens.get(realm);
|
||||
}
|
||||
|
||||
// if no host found, just query for default
|
||||
if (token == null) {
|
||||
token = this.authenticationTokens.get("");
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all authentication tokens.
|
||||
*/
|
||||
public void clearAuthenticationTokens() {
|
||||
this.authenticationTokens.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
try {
|
||||
// Check the against the whitelist and lock out access to the WebView directory
|
||||
// Changing this will cause problems for your application
|
||||
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
|
||||
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
}
|
||||
|
||||
CordovaResourceApi resourceApi = parentEngine.resourceApi;
|
||||
Uri origUri = Uri.parse(url);
|
||||
// Allow plugins to intercept WebView requests.
|
||||
Uri remappedUri = resourceApi.remapUri(origUri);
|
||||
|
||||
if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsKitKatContentUrlFix(origUri)) {
|
||||
CordovaResourceApi.OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
|
||||
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
|
||||
}
|
||||
// If we don't need to special-case the request, let the browser load it.
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
if (!(e instanceof FileNotFoundException)) {
|
||||
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
|
||||
}
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsKitKatContentUrlFix(Uri uri) {
|
||||
return "content".equals(uri.getScheme());
|
||||
}
|
||||
|
||||
private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
|
||||
if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
|
||||
return false;
|
||||
}
|
||||
if (uri.getQuery() != null || uri.getFragment() != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!uri.toString().contains("%")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
package org.apache.cordova.engine;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.apache.cordova.CordovaBridge;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.CordovaPreferences;
|
||||
import org.apache.cordova.CordovaResourceApi;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CordovaWebViewEngine;
|
||||
import org.apache.cordova.ICordovaCookieManager;
|
||||
import org.apache.cordova.LOG;
|
||||
import org.apache.cordova.NativeToJsMessageQueue;
|
||||
import org.apache.cordova.PluginManager;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
|
||||
/**
|
||||
* Glue class between CordovaWebView (main Cordova logic) and SystemWebView (the actual View).
|
||||
* We make the Engine separate from the actual View so that:
|
||||
* A) We don't need to worry about WebView methods clashing with CordovaWebViewEngine methods
|
||||
* (e.g.: goBack() is void for WebView, and boolean for CordovaWebViewEngine)
|
||||
* B) Separating the actual View from the Engine makes API surfaces smaller.
|
||||
* Class uses two-phase initialization. However, CordovaWebView is responsible for calling .init().
|
||||
*/
|
||||
public class SystemWebViewEngine implements CordovaWebViewEngine {
|
||||
public static final String TAG = "SystemWebViewEngine";
|
||||
|
||||
protected final SystemWebView webView;
|
||||
protected final SystemCookieManager cookieManager;
|
||||
protected CordovaPreferences preferences;
|
||||
protected CordovaBridge bridge;
|
||||
protected CordovaWebViewEngine.Client client;
|
||||
protected CordovaWebView parentWebView;
|
||||
protected CordovaInterface cordova;
|
||||
protected PluginManager pluginManager;
|
||||
protected CordovaResourceApi resourceApi;
|
||||
protected NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||
private BroadcastReceiver receiver;
|
||||
|
||||
/** Used when created via reflection. */
|
||||
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||
this(new SystemWebView(context), preferences);
|
||||
}
|
||||
|
||||
public SystemWebViewEngine(SystemWebView webView) {
|
||||
this(webView, null);
|
||||
}
|
||||
|
||||
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
|
||||
this.preferences = preferences;
|
||||
this.webView = webView;
|
||||
cookieManager = new SystemCookieManager(webView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
|
||||
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||
if (this.cordova != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// Needed when prefs are not passed by the constructor
|
||||
if (preferences == null) {
|
||||
preferences = parentWebView.getPreferences();
|
||||
}
|
||||
this.parentWebView = parentWebView;
|
||||
this.cordova = cordova;
|
||||
this.client = client;
|
||||
this.resourceApi = resourceApi;
|
||||
this.pluginManager = pluginManager;
|
||||
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
|
||||
webView.init(this, cordova);
|
||||
|
||||
initWebViewSettings();
|
||||
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
||||
@Override
|
||||
public void setNetworkAvailable(boolean value) {
|
||||
//sometimes this can be called after calling webview.destroy() on destroy()
|
||||
//thus resulting in a NullPointerException
|
||||
if(webView!=null) {
|
||||
webView.setNetworkAvailable(value);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void runOnUiThread(Runnable r) {
|
||||
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
|
||||
}
|
||||
}));
|
||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
|
||||
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
|
||||
exposeJsInterface(webView, bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CordovaWebView getCordovaWebView() {
|
||||
return parentWebView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICordovaCookieManager getCookieManager() {
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return webView;
|
||||
}
|
||||
|
||||
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
|
||||
@SuppressWarnings("deprecation")
|
||||
private void initWebViewSettings() {
|
||||
webView.setInitialScale(0);
|
||||
webView.setVerticalScrollBarEnabled(false);
|
||||
// Enable JavaScript
|
||||
final WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
|
||||
|
||||
String manufacturer = android.os.Build.MANUFACTURER;
|
||||
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||
|
||||
//We don't save any form data in the application
|
||||
settings.setSaveFormData(false);
|
||||
settings.setSavePassword(false);
|
||||
|
||||
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
|
||||
// while we do this
|
||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||
settings.setMediaPlaybackRequiresUserGesture(false);
|
||||
|
||||
// Enable database
|
||||
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
|
||||
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
|
||||
settings.setDatabaseEnabled(true);
|
||||
settings.setDatabasePath(databasePath);
|
||||
|
||||
|
||||
//Determine whether we're in debug or release mode, and turn on Debugging!
|
||||
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
|
||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
enableRemoteDebugging();
|
||||
}
|
||||
|
||||
settings.setGeolocationDatabasePath(databasePath);
|
||||
|
||||
// Enable DOM storage
|
||||
settings.setDomStorageEnabled(true);
|
||||
|
||||
// Enable built-in geolocation
|
||||
settings.setGeolocationEnabled(true);
|
||||
|
||||
// Enable AppCache
|
||||
// Fix for CB-2282
|
||||
settings.setAppCacheMaxSize(5 * 1048576);
|
||||
settings.setAppCachePath(databasePath);
|
||||
settings.setAppCacheEnabled(true);
|
||||
|
||||
// Fix for CB-1405
|
||||
// Google issue 4641
|
||||
String defaultUserAgent = settings.getUserAgentString();
|
||||
|
||||
// Fix for CB-3360
|
||||
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
|
||||
if (overrideUserAgent != null) {
|
||||
settings.setUserAgentString(overrideUserAgent);
|
||||
} else {
|
||||
String appendUserAgent = preferences.getString("AppendUserAgent", null);
|
||||
if (appendUserAgent != null) {
|
||||
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
|
||||
}
|
||||
}
|
||||
// End CB-3360
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
|
||||
if (this.receiver == null) {
|
||||
this.receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
settings.getUserAgentString();
|
||||
}
|
||||
};
|
||||
webView.getContext().registerReceiver(this.receiver, intentFilter);
|
||||
}
|
||||
// end CB-1405
|
||||
}
|
||||
|
||||
private void enableRemoteDebugging() {
|
||||
try {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Yeah, we know. It'd be great if lint was just a little smarter.
|
||||
@SuppressLint("AddJavascriptInterface")
|
||||
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
|
||||
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
|
||||
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the url into the webview.
|
||||
*/
|
||||
@Override
|
||||
public void loadUrl(final String url, boolean clearNavigationStack) {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return webView.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
webView.stopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
webView.clearCache(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory() {
|
||||
webView.clearHistory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoBack() {
|
||||
return webView.canGoBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to previous page in history. (We manage our own history)
|
||||
*
|
||||
* @return true if we went back, false if we are already at top
|
||||
*/
|
||||
@Override
|
||||
public boolean goBack() {
|
||||
// Check webview first to see if there is a history
|
||||
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaused(boolean value) {
|
||||
if (value) {
|
||||
webView.onPause();
|
||||
webView.pauseTimers();
|
||||
} else {
|
||||
webView.onResume();
|
||||
webView.resumeTimers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
webView.chromeClient.destroyLastDialog();
|
||||
webView.destroy();
|
||||
// unregister the receiver
|
||||
if (receiver != null) {
|
||||
try {
|
||||
webView.getContext().unregisterReceiver(receiver);
|
||||
} catch (Exception e) {
|
||||
LOG.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateJavascript(String js, ValueCallback<String> callback) {
|
||||
webView.evaluateJavascript(js, callback);
|
||||
}
|
||||
}
|
30
asciiblaster-cordoba/platforms/android/android.json
Normal file
30
asciiblaster-cordoba/platforms/android/android.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"prepare_queue": {
|
||||
"installed": [],
|
||||
"uninstalled": []
|
||||
},
|
||||
"config_munge": {
|
||||
"files": {
|
||||
"res/xml/config.xml": {
|
||||
"parents": {
|
||||
"/*": [
|
||||
{
|
||||
"xml": "<feature name=\"Whitelist\"><param name=\"android-package\" value=\"org.apache.cordova.whitelist.WhitelistPlugin\" /><param name=\"onload\" value=\"true\" /></feature>",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"installed_plugins": {
|
||||
"cordova-plugin-whitelist": {
|
||||
"PACKAGE_NAME": "com.lalbornoz.asciiblaster"
|
||||
}
|
||||
},
|
||||
"dependent_plugins": {},
|
||||
"modules": [],
|
||||
"plugin_metadata": {
|
||||
"cordova-plugin-whitelist": "1.3.3"
|
||||
}
|
||||
}
|
329
asciiblaster-cordoba/platforms/android/app/build.gradle
Normal file
329
asciiblaster-cordoba/platforms/android/app/build.gradle
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
// Allow plugins to declare Maven dependencies via build-extras.gradle.
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral();
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.1.0'
|
||||
}
|
||||
|
||||
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
|
||||
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
|
||||
ext {
|
||||
apply from: '../CordovaLib/cordova.gradle'
|
||||
// The value for android.compileSdkVersion.
|
||||
if (!project.hasProperty('cdvCompileSdkVersion')) {
|
||||
cdvCompileSdkVersion = null;
|
||||
}
|
||||
// The value for android.buildToolsVersion.
|
||||
if (!project.hasProperty('cdvBuildToolsVersion')) {
|
||||
cdvBuildToolsVersion = null;
|
||||
}
|
||||
// Sets the versionCode to the given value.
|
||||
if (!project.hasProperty('cdvVersionCode')) {
|
||||
cdvVersionCode = null
|
||||
}
|
||||
// Sets the minSdkVersion to the given value.
|
||||
if (!project.hasProperty('cdvMinSdkVersion')) {
|
||||
cdvMinSdkVersion = null
|
||||
}
|
||||
// Whether to build architecture-specific APKs.
|
||||
if (!project.hasProperty('cdvBuildMultipleApks')) {
|
||||
cdvBuildMultipleApks = null
|
||||
}
|
||||
// Whether to append a 0 "abi digit" to versionCode when only a single APK is build
|
||||
if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
|
||||
cdvVersionCodeForceAbiDigit = null
|
||||
}
|
||||
// .properties files to use for release signing.
|
||||
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
|
||||
cdvReleaseSigningPropertiesFile = null
|
||||
}
|
||||
// .properties files to use for debug signing.
|
||||
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
|
||||
cdvDebugSigningPropertiesFile = null
|
||||
}
|
||||
// Set by build.js script.
|
||||
if (!project.hasProperty('cdvBuildArch')) {
|
||||
cdvBuildArch = null
|
||||
}
|
||||
|
||||
// Plugin gradle extensions can append to this to have code run at the end.
|
||||
cdvPluginPostBuildExtras = []
|
||||
}
|
||||
|
||||
// PLUGIN GRADLE EXTENSIONS START
|
||||
// PLUGIN GRADLE EXTENSIONS END
|
||||
|
||||
def hasBuildExtras1 = file('build-extras.gradle').exists()
|
||||
if (hasBuildExtras1) {
|
||||
apply from: 'build-extras.gradle'
|
||||
}
|
||||
|
||||
def hasBuildExtras2 = file('../build-extras.gradle').exists()
|
||||
if (hasBuildExtras2) {
|
||||
apply from: '../build-extras.gradle'
|
||||
}
|
||||
|
||||
// Set property defaults after extension .gradle files.
|
||||
if (ext.cdvCompileSdkVersion == null) {
|
||||
ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget()
|
||||
//ext.cdvCompileSdkVersion = project.ext.defaultCompileSdkVersion
|
||||
}
|
||||
if (ext.cdvBuildToolsVersion == null) {
|
||||
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
|
||||
//ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion
|
||||
}
|
||||
if (ext.cdvDebugSigningPropertiesFile == null && file('../debug-signing.properties').exists()) {
|
||||
ext.cdvDebugSigningPropertiesFile = '../debug-signing.properties'
|
||||
}
|
||||
if (ext.cdvReleaseSigningPropertiesFile == null && file('../release-signing.properties').exists()) {
|
||||
ext.cdvReleaseSigningPropertiesFile = '../release-signing.properties'
|
||||
}
|
||||
|
||||
// Cast to appropriate types.
|
||||
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
|
||||
ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
|
||||
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? defaultMinSdkVersion : Integer.parseInt('' + cdvMinSdkVersion)
|
||||
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
|
||||
|
||||
def computeBuildTargetName(debugBuild) {
|
||||
def ret = 'assemble'
|
||||
if (cdvBuildMultipleApks && cdvBuildArch) {
|
||||
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
|
||||
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
|
||||
}
|
||||
return ret + (debugBuild ? 'Debug' : 'Release')
|
||||
}
|
||||
|
||||
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
|
||||
task cdvBuildDebug
|
||||
cdvBuildDebug.dependsOn {
|
||||
return computeBuildTargetName(true)
|
||||
}
|
||||
|
||||
task cdvBuildRelease
|
||||
cdvBuildRelease.dependsOn {
|
||||
return computeBuildTargetName(false)
|
||||
}
|
||||
|
||||
task cdvPrintProps << {
|
||||
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
|
||||
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
|
||||
println('cdvVersionCode=' + cdvVersionCode)
|
||||
println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
|
||||
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
|
||||
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
|
||||
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
|
||||
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
|
||||
println('cdvBuildArch=' + cdvBuildArch)
|
||||
println('computedVersionCode=' + android.defaultConfig.versionCode)
|
||||
android.productFlavors.each { flavor ->
|
||||
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
|
||||
applicationId privateHelpers.extractStringFromManifest("package")
|
||||
|
||||
if (cdvMinSdkVersion != null) {
|
||||
minSdkVersion cdvMinSdkVersion
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false;
|
||||
}
|
||||
|
||||
compileSdkVersion cdvCompileSdkVersion
|
||||
buildToolsVersion cdvBuildToolsVersion
|
||||
|
||||
// This code exists for Crosswalk and other Native APIs.
|
||||
// By default, we multiply the existing version code in the
|
||||
// Android Manifest by 10 and add a number for each architecture.
|
||||
// If you are not using Crosswalk or SQLite, you can
|
||||
// ignore this chunk of code, and your version codes will be respected.
|
||||
|
||||
if (Boolean.valueOf(cdvBuildMultipleApks)) {
|
||||
flavorDimensions "default"
|
||||
|
||||
productFlavors {
|
||||
armeabi {
|
||||
versionCode defaultConfig.versionCode*10 + 1
|
||||
ndk {
|
||||
abiFilters = ["armeabi"]
|
||||
}
|
||||
}
|
||||
armv7 {
|
||||
versionCode defaultConfig.versionCode*10 + 2
|
||||
ndk {
|
||||
abiFilters = ["armeabi-v7a"]
|
||||
}
|
||||
}
|
||||
arm64 {
|
||||
versionCode defaultConfig.versionCode*10 + 3
|
||||
ndk {
|
||||
abiFilters = ["arm64-v8a"]
|
||||
}
|
||||
}
|
||||
x86 {
|
||||
versionCode defaultConfig.versionCode*10 + 4
|
||||
ndk {
|
||||
abiFilters = ["x86"]
|
||||
}
|
||||
}
|
||||
x86_64 {
|
||||
versionCode defaultConfig.versionCode*10 + 5
|
||||
ndk {
|
||||
abiFilters = ["x86_64"]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
|
||||
// This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
|
||||
defaultConfig {
|
||||
versionCode defaultConfig.versionCode*10
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
if (cdvReleaseSigningPropertiesFile) {
|
||||
signingConfigs {
|
||||
release {
|
||||
// These must be set or Gradle will complain (even if they are overridden).
|
||||
keyAlias = ""
|
||||
keyPassword = "__unset" // And these must be set to non-empty in order to have the signing step added to the task graph.
|
||||
storeFile = null
|
||||
storePassword = "__unset"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
|
||||
}
|
||||
if (cdvDebugSigningPropertiesFile) {
|
||||
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* WARNING: Cordova Lib and platform scripts do management inside of this code here,
|
||||
* if you are adding the dependencies manually, do so outside the comments, otherwise
|
||||
* the Cordova tools will overwrite them
|
||||
*/
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
// SUB-PROJECT DEPENDENCIES START
|
||||
implementation(project(path: ":CordovaLib"))
|
||||
// SUB-PROJECT DEPENDENCIES END
|
||||
}
|
||||
|
||||
def promptForReleaseKeyPassword() {
|
||||
if (!cdvReleaseSigningPropertiesFile) {
|
||||
return;
|
||||
}
|
||||
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
|
||||
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
|
||||
}
|
||||
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
|
||||
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
|
||||
}
|
||||
}
|
||||
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
taskGraph.getAllTasks().each() { task ->
|
||||
if(['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX76Release'].contains(task.name)) {
|
||||
promptForReleaseKeyPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addSigningProps(propsFilePath, signingConfig) {
|
||||
def propsFile = file(propsFilePath)
|
||||
def props = new Properties()
|
||||
propsFile.withReader { reader ->
|
||||
props.load(reader)
|
||||
}
|
||||
|
||||
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
|
||||
if (!storeFile.isAbsolute()) {
|
||||
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
|
||||
}
|
||||
if (!storeFile.exists()) {
|
||||
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
|
||||
}
|
||||
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
|
||||
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
|
||||
signingConfig.storeFile = storeFile
|
||||
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
|
||||
def storeType = props.get('storeType', props.get('key.store.type', ''))
|
||||
if (!storeType) {
|
||||
def filename = storeFile.getName().toLowerCase();
|
||||
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
|
||||
storeType = 'pkcs12'
|
||||
} else {
|
||||
storeType = signingConfig.storeType // "jks"
|
||||
}
|
||||
}
|
||||
signingConfig.storeType = storeType
|
||||
}
|
||||
|
||||
for (def func : cdvPluginPostBuildExtras) {
|
||||
func()
|
||||
}
|
||||
|
||||
// This can be defined within build-extras.gradle as:
|
||||
// ext.postBuildExtras = { ... code here ... }
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest android:hardwareAccelerated="true" android:versionCode="10003" android:versionName="1.0.3" package="com.lalbornoz.asciiblaster" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application android:hardwareAccelerated="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:supportsRtl="true">
|
||||
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter android:label="@string/launcher_name">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27" />
|
||||
</manifest>
|
@ -0,0 +1,139 @@
|
||||
.fa{color:#fff}.fb{color:#000}.fc{color:#00007F}.fd{color:#009300}.fe{color:red}.ff{color:#7f0000}.fg{color:#9C009C}.fh{color:#FC7F00}.fi{color:#FF0}.fj{color:#00FC00}.fk{color:#009393}.fl{color:#0FF}.fm{color:#0000FC}.fn{color:#F0F}.fo{color:#7F7F7F}.fp{color:#D2D2D2}
|
||||
.ba{background-color:#fff}.bb{background-color:#000}.bc{background-color:#00007F}.bd{background-color:#009300}.be{background-color:red}.bf{background-color:#7f0000}.bg{background-color:#9C009C}.bh{background-color:#FC7F00}.bi{background-color:#FF0}.bj{background-color:#00FC00}.bk{background-color:#009393}.bl{background-color:#0FF}.bm{background-color:#0000FC}.bn{background-color:#F0F}.bo{background-color:#7F7F7F}.bp{background-color:#D2D2D2}
|
||||
|
||||
.winxp .fa{color:rgb(255,255,255)}
|
||||
.winxp .fb{color:rgb(0,0,0)}
|
||||
.winxp .fc{color:rgb(0,0,128)}
|
||||
.winxp .fd{color:rgb(0,128,0)}
|
||||
.winxp .fe{color:rgb(255,0,0)}
|
||||
.winxp .ff{color:rgb(128,0,0)}
|
||||
.winxp .fg{color:rgb(128,0,128)}
|
||||
.winxp .fh{color:rgb(255,128,0)}
|
||||
.winxp .fi{color:rgb(255,255,0)}
|
||||
.winxp .fj{color:rgb(0,255,0)}
|
||||
.winxp .fk{color:rgb(0,128,128)}
|
||||
.winxp .fl{color:rgb(0,255,255)}
|
||||
.winxp .fm{color:rgb(0,0,255)}
|
||||
.winxp .fn{color:rgb(255,0,255)}
|
||||
.winxp .fo{color:rgb(128,128,128)}
|
||||
.winxp .fp{color:rgb(192,192,192)}
|
||||
|
||||
.winxp .ba{background-color:rgb(255,255,255)}
|
||||
.winxp .bb{background-color:rgb(0,0,0)}
|
||||
.winxp .bc{background-color:rgb(0,0,128)}
|
||||
.winxp .bd{background-color:rgb(0,128,0)}
|
||||
.winxp .be{background-color:rgb(255,0,0)}
|
||||
.winxp .bf{background-color:rgb(128,0,0)}
|
||||
.winxp .bg{background-color:rgb(128,0,128)}
|
||||
.winxp .bh{background-color:rgb(255,128,0)}
|
||||
.winxp .bi{background-color:rgb(255,255,0)}
|
||||
.winxp .bj{background-color:rgb(0,255,0)}
|
||||
.winxp .bk{background-color:rgb(0,128,128)}
|
||||
.winxp .bl{background-color:rgb(0,255,255)}
|
||||
.winxp .bm{background-color:rgb(0,0,255)}
|
||||
.winxp .bn{background-color:rgb(255,0,255)}
|
||||
.winxp .bo{background-color:rgb(128,128,128)}
|
||||
.winxp .bp{background-color:rgb(192,192,192)}
|
||||
|
||||
.vga .fa{color:rgb(255,255,255)}
|
||||
.vga .fb{color:rgb(0,0,0)}
|
||||
.vga .fc{color:rgb(0,0,170)}
|
||||
.vga .fd{color:rgb(0,170,0)}
|
||||
.vga .fe{color:rgb(255,85,85)}
|
||||
.vga .ff{color:rgb(170,0,0)}
|
||||
.vga .fg{color:rgb(170,0,170)}
|
||||
.vga .fh{color:rgb(170,85,0)}
|
||||
.vga .fi{color:rgb(255,255,85)}
|
||||
.vga .fj{color:rgb(85,255,85)}
|
||||
.vga .fk{color:rgb(0,170,170)}
|
||||
.vga .fl{color:rgb(85,255,255)}
|
||||
.vga .fm{color:rgb(85,85,255)}
|
||||
.vga .fn{color:rgb(255,85,255)}
|
||||
.vga .fo{color:rgb(85,85,85)}
|
||||
.vga .fp{color:rgb(170,170,170)}
|
||||
|
||||
|
||||
.vga .ba{background-color:rgb(255,255,255)}
|
||||
.vga .bb{background-color:rgb(0,0,0)}
|
||||
.vga .bc{background-color:rgb(0,0,170)}
|
||||
.vga .bd{background-color:rgb(0,170,0)}
|
||||
.vga .be{background-color:rgb(255,85,85)}
|
||||
.vga .bf{background-color:rgb(170,0,0)}
|
||||
.vga .bg{background-color:rgb(170,0,170)}
|
||||
.vga .bh{background-color:rgb(170,85,0)}
|
||||
.vga .bi{background-color:rgb(255,255,85)}
|
||||
.vga .bj{background-color:rgb(85,255,85)}
|
||||
.vga .bk{background-color:rgb(0,170,170)}
|
||||
.vga .bl{background-color:rgb(85,255,255)}
|
||||
.vga .bm{background-color:rgb(85,85,255)}
|
||||
.vga .bn{background-color:rgb(255,85,255)}
|
||||
.vga .bo{background-color:rgb(85,85,85)}
|
||||
.vga .bp{background-color:rgb(170,170,170)}
|
||||
|
||||
.c64 .fa{color:rgb(255,255,255)}
|
||||
.c64 .fb{color:rgb(0,0,0)}
|
||||
.c64 .fc{color:rgb(69,32,170)}
|
||||
.c64 .fd{color:rgb(101,170,69)}
|
||||
.c64 .fe{color:rgb(138,101,32)}
|
||||
.c64 .ff{color:rgb(138,69,32)}
|
||||
.c64 .fg{color:rgb(138,69,170)}
|
||||
.c64 .fh{color:rgb(101,69,0)}
|
||||
.c64 .fi{color:rgb(207,207,101)}
|
||||
.c64 .fj{color:rgb(170,239,138)}
|
||||
.c64 .fk{color:rgb(138,138,138)}
|
||||
.c64 .fl{color:rgb(101,170,207)}
|
||||
.c64 .fm{color:rgb(138,101,223)}
|
||||
.c64 .fn{color:rgb(207,138,101)}
|
||||
.c64 .fo{color:rgb(69,69,69)}
|
||||
.c64 .fp{color:rgb(170,170,170)}
|
||||
|
||||
.c64 .ba{background-color:rgb(255,255,255)}
|
||||
.c64 .bb{background-color:rgb(0,0,0)}
|
||||
.c64 .bc{background-color:rgb(69,32,170)}
|
||||
.c64 .bd{background-color:rgb(101,170,69)}
|
||||
.c64 .be{background-color:rgb(138,101,32)}
|
||||
.c64 .bf{background-color:rgb(138,69,32)}
|
||||
.c64 .bg{background-color:rgb(138,69,170)}
|
||||
.c64 .bh{background-color:rgb(101,69,0)}
|
||||
.c64 .bi{background-color:rgb(207,207,101)}
|
||||
.c64 .bj{background-color:rgb(170,239,138)}
|
||||
.c64 .bk{background-color:rgb(138,138,138)}
|
||||
.c64 .bl{background-color:rgb(101,170,207)}
|
||||
.c64 .bm{background-color:rgb(138,101,223)}
|
||||
.c64 .bn{background-color:rgb(207,138,101)}
|
||||
.c64 .bo{background-color:rgb(69,69,69)}
|
||||
.c64 .bp{background-color:rgb(170,170,170)}
|
||||
|
||||
.appleii .fa{color:rgb(255,255,255)}
|
||||
.appleii .fb{color:rgb(0,0,0)}
|
||||
.appleii .fc{color:rgb(64,53,121)}
|
||||
.appleii .fd{color:rgb(64,75,7)}
|
||||
.appleii .fe{color:rgb(191,180,248)}
|
||||
.appleii .ff{color:rgb(109,41,64)}
|
||||
.appleii .fg{color:rgb(218,60,241)}
|
||||
.appleii .fh{color:rgb(218,104,15)}
|
||||
.appleii .fi{color:rgb(191,202,134)}
|
||||
.appleii .fj{color:rgb(38,195,16)}
|
||||
.appleii .fk{color:rgb(19,87,64)}
|
||||
.appleii .fl{color:rgb(146,214,191)}
|
||||
.appleii .fm{color:rgb(37,151,240)}
|
||||
.appleii .fn{color:rgb(236,168,191)}
|
||||
.appleii .fo{color:rgb(128,128,128)}
|
||||
.appleii .fp{color:rgb(128,128,128)}
|
||||
|
||||
.appleii .ba{background-color:rgb(255,255,255)}
|
||||
.appleii .bb{background-color:rgb(0,0,0)}
|
||||
.appleii .bc{background-color:rgb(64,53,121)}
|
||||
.appleii .bd{background-color:rgb(64,75,7)}
|
||||
.appleii .be{background-color:rgb(191,180,248)}
|
||||
.appleii .bf{background-color:rgb(109,41,64)}
|
||||
.appleii .bg{background-color:rgb(218,60,241)}
|
||||
.appleii .bh{background-color:rgb(218,104,15)}
|
||||
.appleii .bi{background-color:rgb(191,202,134)}
|
||||
.appleii .bj{background-color:rgb(38,195,16)}
|
||||
.appleii .bk{background-color:rgb(19,87,64)}
|
||||
.appleii .bl{background-color:rgb(146,214,191)}
|
||||
.appleii .bm{background-color:rgb(37,151,240)}
|
||||
.appleii .bn{background-color:rgb(236,168,191)}
|
||||
.appleii .bo{background-color:rgb(128,128,128)}
|
||||
.appleii .bp{background-color:rgb(128,128,128)}
|
@ -0,0 +1,119 @@
|
||||
.h1 { font-size: 32px }
|
||||
.h2 { font-size: 24px }
|
||||
.h3 { font-size: 18.72px }
|
||||
.h4 { font-size: 16px }
|
||||
.h5 { font-size: 13.28px }
|
||||
.h6 { font-size: 10.72px }
|
||||
|
||||
[class^=h] {
|
||||
font-weight: bold;
|
||||
display:block;
|
||||
margin-top:8px;
|
||||
margin-bottom:8px;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background:#000;
|
||||
background-image:url('../images/tile.jpg');
|
||||
font-size:16pt;
|
||||
font-family:times new roman;
|
||||
color:rgb(255,255,0);
|
||||
}
|
||||
|
||||
i {
|
||||
color:#0f0!important;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color:rgb(0,255,255);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color:#00f;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.code {
|
||||
white-space:pre-wrap;
|
||||
background: rgba(128,0,128,.3);
|
||||
color: rgb(255,0,255);
|
||||
font-size: 16pt;
|
||||
font-family: Consolas, Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
.inline {
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.block {
|
||||
padding:12px 16px;
|
||||
border-width: 0 0 0 5px;
|
||||
border-style: solid;
|
||||
border-color:#0f0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0px;
|
||||
border:3px solid #00f;
|
||||
margin:10px 0;
|
||||
font-size:20px;
|
||||
color:#0ff!important;
|
||||
font-size:12pt!important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 6px 9px;
|
||||
border:2px solid #00f
|
||||
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size:42px;
|
||||
margin-bottom:15px;
|
||||
color:#f0f!important
|
||||
}
|
||||
|
||||
h2 {
|
||||
color:#0ff!important;
|
||||
margin-top:15px;
|
||||
margin-bottom:15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color:#f00!important
|
||||
}
|
||||
|
||||
h4 {
|
||||
color:#0f0!important
|
||||
}
|
||||
|
||||
h5 {
|
||||
color:#00f!important
|
||||
}
|
||||
|
||||
a:visited i {
|
||||
color:#00f!important;
|
||||
}
|
||||
|
||||
a i {
|
||||
color:#f0f!important
|
||||
}
|
||||
|
||||
a:active i {
|
||||
color:#ff0!important
|
||||
}
|
||||
|
||||
/*
|
||||
.logo {
|
||||
background:url('https://jollo.org/assets/images/logo/rebomine_logo.gif');
|
||||
width:255px;
|
||||
height:107px;
|
||||
float:right;
|
||||
}
|
||||
*/
|
@ -0,0 +1,211 @@
|
||||
textarea,input[type=text],body {
|
||||
margin:0;
|
||||
font-family: 'FixedsysExcelsior301Regular';
|
||||
font-size: 12pt;
|
||||
font-weight: 100;
|
||||
line-height: 11pt;
|
||||
color: #6d6b6d;
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
}
|
||||
body {
|
||||
background-color: #000000 !important;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'FixedsysExcelsior301Regular';
|
||||
src: url('../fonts/fsex300-webfont.eot');
|
||||
src: url('../fonts/fsex300-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/fsex300-webfont.woff') format('woff'),
|
||||
url('../fonts/fsex300-webfont.ttf') format('truetype'),
|
||||
url('../fonts/fsex300-webfont.svg#FixedsysExcelsior301Regular') format('svg');
|
||||
font-weighmal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
a {display: block}
|
||||
a:link, a:visited {text-decoration: none; color: #6b6760}
|
||||
a:hover { text-decoration: underline }
|
||||
|
||||
.faded { color: #404040; }
|
||||
.rapper, .block {
|
||||
float: left;
|
||||
height:auto;
|
||||
width:auto;
|
||||
background-color: #000000;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.rapper {
|
||||
white-space:pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
#ui_rapper .block {
|
||||
width: 100px;
|
||||
}
|
||||
.block {
|
||||
padding:4px;
|
||||
}
|
||||
.block:nth-child(n+2) {
|
||||
padding-left: 30px;
|
||||
}
|
||||
#textarea_mode { padding: 4px; }
|
||||
.tool {
|
||||
cursor: pointer;
|
||||
}
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
.tool.radio {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
.tool.radio.focused {
|
||||
color: #000;
|
||||
|
||||
background-color: #6d6d6d;
|
||||
box-shadow: none;
|
||||
}
|
||||
.transparent {
|
||||
background-color: transparent;
|
||||
background-image: url(../images/gray-dither.gif);
|
||||
background-size: 8px 8px;
|
||||
}
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 2) {
|
||||
.transparent {
|
||||
background-size: 4px 4px;
|
||||
}
|
||||
}
|
||||
span,a { min-width: 8px; line-height: 15px; display: inline-block; }
|
||||
body.pixels {
|
||||
line-height: 8px;
|
||||
}
|
||||
.pixels #brush_rapper span,
|
||||
.pixels #brush_rapper a,
|
||||
.pixels #canvas_rapper span,
|
||||
.pixels #canvas_rapper a { line-height: 8px; overflow: hidden; }
|
||||
.rapper { cursor: crosshair; }
|
||||
body.grid span { border-right: 1px solid #444; border-bottom: 1px solid #444; }
|
||||
body.grid div { border-left: 1px solid #444; }
|
||||
body.grid #canvas_rapper > div:first-child,
|
||||
body.grid #palette_rapper > div:first-child,
|
||||
body.grid #letters_rapper > div:first-child,
|
||||
body.grid #brush_rapper > div:first-child { border-top: 1px solid #444; }
|
||||
body.grid .tool { border: 1px solid #444; }
|
||||
.ed { color: #fff; }
|
||||
.locked { border-bottom: 1px solid; color: #bbb; text-decoration: none; }
|
||||
.tool.locked.focused { box-shadow: 0 0; }
|
||||
.focused { box-shadow: inset 0 0px 2px #fff; border-color: #fff; }
|
||||
.ba.focused { box-shadow: inset 0 0px 2px #000, inset 0 0px 2px #000; border-color: #000; }
|
||||
.tool.focused, .ed.focused { color: white; text-decoration: underline; }
|
||||
.focused { box-shadow: inset 1px 0 2px white, inset -1px 0 2px white, inset 0 1px 2px white, inset 0 -1px 2px white; }
|
||||
.faba.focused, .fbba.focused, .fcba.focused, .fdba.focused, .feba.focused, .ffba.focused, .fgba.focused, .fhba.focused,
|
||||
.fiba.focused, .fjba.focused, .fkba.focused, .flba.focused, .fmba.focused, .fnba.focused, .foba.focused, .fpba.focused
|
||||
{ box-shadow: inset 1px 0 2px #888, inset -1px 0 2px #888, inset 0 1px 2px #888, inset 0 -1px 2px #888; }
|
||||
body.loading { opacity: 0; }
|
||||
body { transition: 0.1s linear; }
|
||||
#import_textarea { font-size: 9pt; }
|
||||
textarea { font-size:12pt; width: 37vw; height: 300px; background: #333; color: #0f0; border: 0; font-family: 'FixedsysExcelsior301Regular'; outline: 0; border: 1px solid #333; background:#010;}
|
||||
#shader_rapper { display: none; }
|
||||
#import_rapper { display: none; }
|
||||
#canvas_rapper {
|
||||
white-space: pre;
|
||||
box-shadow: 0 0 2px rgba(255,255,255,0.3);
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
#ui_rapper { clear:both; float: left; width: 100vw; }
|
||||
#workspace_rapper { width: 100%; }
|
||||
|
||||
.loading .vertical #ui_rapper { clear: none }
|
||||
.vertical #ui_rapper { width: 320px; float: left; clear: none; }
|
||||
.vertical .rapper, .vertical .block { float: left; }
|
||||
.vertical #canvas_rapper,
|
||||
.vertical #canvas_rapper div,
|
||||
.vertical #tools_rapper,
|
||||
.vertical #palette_rapper,
|
||||
.vertical #brush_container { display: inline-block; float: left; }
|
||||
.vertical #workspace_rapper { width: auto; position: relative; float: left; }
|
||||
.vertical #palette_rapper { margin-right: 10px; }
|
||||
.vertical #tools_block { min-width: 100%; }
|
||||
|
||||
#secret_rapper { float: left; clear: right; }
|
||||
#secret_rapper span { float: left; }
|
||||
.vertical #secret_rapper { margin-right: 10px; }
|
||||
.vertical #secret_rapper span { float: left; clear: both; }
|
||||
.nopaint #brush_rapper { min-height: 70px; min-width: 50px; }
|
||||
|
||||
#nopaint_rapper.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rotated #canvas_rapper {
|
||||
transform: translateX(-50%) translateY(-50%) translateZ(0) rotate(-90deg);
|
||||
transform-origin: 50% 50%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
#tools_block > * {
|
||||
cursor: crosshair;
|
||||
}
|
||||
#brush_rapper, #letters_rapper {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.dropper #canvas_rapper {
|
||||
cursor: url(../images/dropper.gif) 0 15, auto;
|
||||
}
|
||||
.bucket #canvas_rapper {
|
||||
cursor: url(../images/bucket.png) 3 15, auto;
|
||||
}
|
||||
#brush_rapper {
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
margin-bottom: 13px;
|
||||
float: left;
|
||||
}
|
||||
#letters_rapper {
|
||||
display: inline-block;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.close { position: absolute; top: 20px; right: 20px; z-index: 2; padding: 10px; background: black; cursor: pointer; }
|
||||
#experimental_palette_toggle.focused { box-shadow: none; }
|
||||
#cursor_input { position: fixed; top: 0; right: 0; width:30px; opacity: 0; font-size: 16px; }
|
||||
.selector_el {
|
||||
border: 1px dashed #fff !important;
|
||||
padding-top: 1px;
|
||||
position:absolute;
|
||||
margin-top: -1px;
|
||||
top:-999px;left:-999px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.selector_el.dragging {
|
||||
color: #0f0;
|
||||
}
|
||||
.selector_el.creating div {
|
||||
display: none;
|
||||
}
|
||||
.custom {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
@keyframes rainbow {
|
||||
0% { color: hsl(0,100%,50%) }
|
||||
33% { color: hsl(90,100%,50%) }
|
||||
50% { color: #fff }
|
||||
66% { color: hsl(320,100%,50%) }
|
||||
100% { color: hsl(360,100%,50%) }
|
||||
}
|
||||
|
||||
.panke #shader_el,
|
||||
.panke #load_el,
|
||||
.panke #import_textarea,
|
||||
.panke #doc_el,
|
||||
.panke #export_button,
|
||||
.panke #grid_el,
|
||||
.panke #save_el,
|
||||
.panke #vertical_checkbox,
|
||||
.panke #add_custom_el,
|
||||
.panke #format_el { display: none !important; }
|
@ -0,0 +1,38 @@
|
||||
<!-- https://raw.githubusercontent.com/lalbornoz/asciiblaster/master/LICENCE -->
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="../css/sally.css" type="text/css" charset="utf-8" />
|
||||
<link rel="stylesheet" href="../css/ak.css" type="text/css" charset="utf-8" />
|
||||
<link rel="stylesheet" href="../css/nitelite.css" type="text/css" charset="utf-8" />
|
||||
<style>
|
||||
body { font-family: 'FixedsysExcelsior301Regular'; }
|
||||
a:nth-of-type(2n+1), a { color: #0f0; }
|
||||
a:nth-of-type(2n+2), a { color: #ff0; }
|
||||
</style>
|
||||
|
||||
<br><br>
|
||||
<center>
|
||||
|
||||
<table border=35 cellpadding=10>
|
||||
<tr><td style="background: rgba(0,0,100,0.5)">
|
||||
<h1>asdf.us/ascii documentation</h1>
|
||||
|
||||
<span style="white-space: pre; color: white;">
|
||||
These are some handy documents which address some of the more obscure
|
||||
features of the asdf.us color code tool:
|
||||
|
||||
* <a href="tips.txt">tips.txt</a> - Tips on using the keyboard
|
||||
* <a href="irssi.txt">irssi.txt</a> - Instructions on using IRSSI to make color codes.
|
||||
* <a href="nopaint.txt">nopaint.txt</a> - A guide to "No Paint"
|
||||
|
||||
Documents on Shaders
|
||||
|
||||
* <a href="shadetut.txt">shadetut.txt</a> - A brief tutorial on ASCII shaders.
|
||||
* <a href="shaders/brush.txt">shaders/brush.txt</a> - Shaders designed to work on the brush
|
||||
* <a href="shaders/canvas.txt">shaders/canvas.txt</a> - Shaders designed to work on the canvas
|
||||
* <a href="shaders/util.txt">shaders/util.txt</a> - Miscellaneous utilities / snippets
|
||||
|
||||
For more information on IRC, Color Codes, and much more, visit the
|
||||
<a href="http://jollo.org/LNT/doc/">documentation sitemap</a>, part of the <a href="http://jollo.org/">Jollo IRC Network</a>.
|
||||
|
||||
<a href="/ascii/">asdf.us/ascii</a>
|
||||
|
@ -0,0 +1,153 @@
|
||||
__________________________________________________________________________
|
||||
____ ____ _____ ____ ___ ___ ____
|
||||
/_____ _____/ / ___ \ / ____/ / ____/ /_____ _____/
|
||||
/ / / / \ \ / / / / / /
|
||||
/ / / /____/ / \ \__ \ \__ / /
|
||||
/ / / ___ __/ \__ \ \__ \ / /
|
||||
/ / / / \ \ \ \ \ \ / /
|
||||
_____/ /_____ / / \ \ ____/ / ____/ /____/ /_____
|
||||
___/ /__/ /_______\ \__/ /___/ // /____
|
||||
__________________________________________________________________________
|
||||
|
||||
|
||||
OPTIMIZE YOUR TERMINAL FOR COLOR CODES ON OSX
|
||||
=============================================
|
||||
|
||||
You can use terminal, some nerds seem to prefer iterm2, but it's up to you
|
||||
... http://iterm2.com/
|
||||
|
||||
To see color codes correctly, make sure your term type is xterm-256color --
|
||||
|
||||
If you use iterm: https://s3.amazonaws.com/luckyplop/a1b0f0e3d6eae746c82194876f2ccd8b200bc3bb.png
|
||||
If you use terminal: https://s3.amazonaws.com/luckyplop/6a2270b58ea1cfac587607215e1b829f41d47355.png
|
||||
|
||||
Restart iterm after changing this setting.
|
||||
|
||||
The default iterm colors are kind of ugly for color codes, so you may want to change them
|
||||
to something like this..
|
||||
|
||||
https://s3.amazonaws.com/luckyplop/c5f3a1f2b8e2f8a745fa2638c21af7d26117b91b.png
|
||||
|
||||
You can download this iTerm color preset here:
|
||||
|
||||
http://asdf.us/ascii/doc/bamboo.itermcolors
|
||||
|
||||
|
||||
INSTALLING IRSSI ON OSX
|
||||
=======================
|
||||
|
||||
For me the easiest thing is to install homebrew >> http://brew.sh/
|
||||
|
||||
Follow les instructions and then..
|
||||
|
||||
brew install irssi
|
||||
|
||||
Then you run irssi from a terminal by typing the magic word..
|
||||
|
||||
irssi
|
||||
|
||||
|
||||
SETTING UP IRSSI FOR COLOR CODES
|
||||
================================
|
||||
|
||||
Use these commands for proper unicode support --
|
||||
|
||||
/set term_charset utf-8
|
||||
/set recode_autodetect_utf8 ON
|
||||
/set recode_fallback ISO-8859-15
|
||||
/set recode ON
|
||||
|
||||
Use these commands to dump color codes quickly and efficiently --
|
||||
|
||||
/set cmd_queue_speed 0msec
|
||||
/set cmds_max_at_once 1
|
||||
/set flood_max_msgs 0
|
||||
/set flood_timecheck 0
|
||||
|
||||
To make your log go back very far --
|
||||
|
||||
/set scrollback_lines 20000
|
||||
/set scrollback_time 10day
|
||||
|
||||
Remember to type /save after doing a /set to save your changes!
|
||||
|
||||
/save
|
||||
|
||||
Your irssi configuration will be stored in your home directory in..
|
||||
|
||||
.irssi/config
|
||||
|
||||
|
||||
NORMAL IRSSI OPERATION
|
||||
======================
|
||||
|
||||
If you do not want to do the autojoin thing these are the commands you'd normally use to connect:
|
||||
|
||||
/server -ssl irc.asdf.us 7777
|
||||
/join #ascii
|
||||
|
||||
|
||||
SETTING UP IRSSI TO AUTOJOIN #ASCII
|
||||
===================================
|
||||
|
||||
First run irssi, then paste in these commands.
|
||||
Please change YOUR_NICK_HERE to your preferred username!
|
||||
|
||||
/network add -nick YOUR_NICK_HERE -user YOUR_NICK_HERE -realname "YOUR NAME HERE" asdf
|
||||
/server add -network asdf -auto -ssl irc.asdf.us 7777
|
||||
/channel add -auto #ascii asdf
|
||||
/save
|
||||
/quit
|
||||
|
||||
Now run irssi again.. it should autoconnect to the channels and stuff.
|
||||
If you want it to move you into #ascii by default, you can do ctrl-N and then
|
||||
|
||||
/layout save
|
||||
/save
|
||||
|
||||
|
||||
IRC TIPS
|
||||
========
|
||||
|
||||
/join #ascii -- join a channel :)
|
||||
/part #ascii -- leave a channel ;(
|
||||
/quit blabla -- quit irc (with the quit message 'blabla')
|
||||
/list -- list channels
|
||||
/nick booboo -- change your nick to booboo
|
||||
/who #ascii -- show complete list of people on #ascii
|
||||
/names -- show quick list of names
|
||||
/msg nick blabla -- send someone a private message ;)
|
||||
|
||||
|
||||
IRSSI TIPS
|
||||
==========
|
||||
|
||||
Ctrl-N -- move to next window
|
||||
Ctrl-P -- move to previous window
|
||||
/window close -- close a window
|
||||
Fn-up arrow -- page up (if you don't have a pageup key)
|
||||
Fn-down arrow -- page down (if you don't have a pagedown key)
|
||||
|
||||
|
||||
IRSSI SCRIPTING
|
||||
===============
|
||||
|
||||
This is its own can of worms. May we suggest:
|
||||
|
||||
http://scripts.irssi.org/scripts/noticelogic.pl
|
||||
http://scripts.irssi.org/scripts/nickcolor.pl
|
||||
|
||||
To make it run on startup, copy it into ~/.irssi/scripts/autorun/ (and restart irssi)
|
||||
|
||||
Another fun IRC thing is running an XDCC server for sharing files..
|
||||
-- for more info see http://asdf.us/xdcc/
|
||||
|
||||
|
||||
AND REMEMBER...
|
||||
===============
|
||||
|
||||
Have fun and be safe online!
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
|
||||
"" 88
|
||||
9,88m, ,8888, 9,88m, ,888, mm 9,88m, 8888
|
||||
88 88 86 98 88 88 ,mm88 88 88 88 88
|
||||
88 88 '8888' 88888' "nn89 88 88 88 "8m
|
||||
88 a brief tutorial :)
|
||||
|
||||
Last month or so I encountered Jeffrey Scudder's tool 'No Paint' -
|
||||
|
||||
https://www.nopaint.org/
|
||||
|
||||
- an automatic drawing tool with a minimal interface: you control it using
|
||||
just two buttons. The No Paint tool provided much entertainment on #sally,
|
||||
so during some downtime I added similar functionality to the asdf.us/ascii
|
||||
tool. Under the brush, you should see two buttons - to kick it off, click
|
||||
'paint' and it will begin drawing.
|
||||
|
||||
If you don't like what it's doing, click 'no' -
|
||||
- this will remove the current line and start drawing a new line.
|
||||
|
||||
If you like what it's doing, click 'paint' -
|
||||
- the line will be applied to the canvas, and it will start drawing anew.
|
||||
|
||||
While it's going, you can also click 'pause' and it will stop, so you can
|
||||
save it or draw on it yourself.
|
||||
|
||||
|
||||
Keyboard shortcuts -
|
||||
|
||||
left arrow - 'no'
|
||||
right arrow - 'paint'
|
||||
down arrow - 'pause'
|
||||
|
||||
|
||||
Right-click toggles -
|
||||
|
||||
If you RIGHT-CLICK on "Paint" it will switch tools automatically.
|
||||
|
||||
If you RIGHT-CLICK on "No" it will engage TURBO MODE.
|
||||
|
||||
|
||||
Some tools currently implemented -
|
||||
|
||||
- solid brush
|
||||
- erase brush
|
||||
- color-changing brush
|
||||
- hue brush
|
||||
- letter brush
|
||||
- clone tool
|
||||
- smear tool
|
||||
- fill tool
|
||||
- stars brush
|
||||
- canvas slide
|
||||
- canvas scale
|
||||
- canvas rotate
|
||||
- canvas colorcycle
|
||||
|
||||
|
@ -0,0 +1,126 @@
|
||||
BRUSH SHADERS
|
||||
=============
|
||||
|
||||
Unless noted, these shaders were written to work on the brush itself.
|
||||
Make sure "brush" is selected and "animate" is checked.
|
||||
|
||||
|
||||
>> distressed texture brush
|
||||
|
||||
Sample use of the "choice" function to get a random color.
|
||||
|
||||
var char = choice(" abcdef ")
|
||||
lex.bg = +choice("0124")
|
||||
lex.fg = +choice("01234")
|
||||
lex.char = char
|
||||
lex.opacity = char == " " ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> foggy terrain brush
|
||||
|
||||
var char = choice(" abcdef ")
|
||||
lex.bg = choice([14,15])
|
||||
lex.fg = choice("367")
|
||||
lex.char = char
|
||||
lex.opacity = char == " " ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> mirror brush (left-right)
|
||||
|
||||
NOTE: Animate this on the canvas, then draw:
|
||||
|
||||
if (x > w/2) {
|
||||
lex.assign( canvas.aa[y][w-x] )
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> mirror brush (up-down)
|
||||
|
||||
NOTE: Animate this on the canvas, then draw:
|
||||
|
||||
if (x > h/2) {
|
||||
lex.assign( canvas.aa[h-y][x] )
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> rainbow stardust brush
|
||||
|
||||
Uncheck BG and animate this to brush:
|
||||
|
||||
lex.fg = hue(t)
|
||||
lex.char = choice(" ,'.,.','****** ")
|
||||
|
||||
|
||||
|
||||
>> noise brushes, works on a black background:
|
||||
|
||||
lex.bg = max(5, yellow(randint(t)))
|
||||
lex.opacity = lex.bg == colors.black ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> simple rainbow:
|
||||
|
||||
if (lex.bg != 1) lex.bg = randint(t)
|
||||
lex.opacity = lex.bg == colors.black ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> self-erasing:
|
||||
|
||||
if (lex.bg != 1) lex.bg = yellow(randint(t))
|
||||
lex.opacity = lex.bg == colors.black ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> cycling rainbow brush
|
||||
|
||||
if (lex.bg != 1) lex.bg = hue( all_color_hue_order.indexOf( color_names[ lex.bg ] ) + 1 )
|
||||
lex.opacity = lex.bg == colors.black ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> "stars" brush.. set your brush to paint just the character "#"
|
||||
|
||||
if (lex.char == "#") {
|
||||
lex.fg = hue(randint(15))
|
||||
lex.char = random() > 0.1 ? " " : "+@*.,\"+'*-"[randint(10)]
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> use fg char to mask mask what you're drawing on the bg
|
||||
|
||||
if (lex.char != "/") { lex.bg = 1 }
|
||||
|
||||
|
||||
|
||||
>> sharded glitch brush
|
||||
|
||||
Example: http://asdf.us/z/kksnvs.png
|
||||
|
||||
Use on a brush:
|
||||
|
||||
lex.bg = t/y/x
|
||||
lex.opacity = lex.bg % 1 ? 0 : 1
|
||||
|
||||
|
||||
|
||||
>> incremental brush
|
||||
|
||||
Set your brush to be the ^ character, square, about 10x10
|
||||
Draw "char" only
|
||||
Then animate this shader on the canvas:
|
||||
|
||||
if (lex.char=="^") {
|
||||
lex.bg += 1
|
||||
lex.char = " "
|
||||
}
|
||||
lex.bg += 1
|
||||
|
||||
|
||||
|
@ -0,0 +1,237 @@
|
||||
CANVAS SHADERS
|
||||
==============
|
||||
|
||||
These shaders were written to work on areas of canvas.
|
||||
Make sure "canvas" is selected and "animate" is checked.
|
||||
|
||||
|
||||
>> original shader..
|
||||
|
||||
lex.bg = hue((x+y*y+t/10)/20)
|
||||
lex.fg = (x+y)%16
|
||||
lex.char = (y%2) ? ":" : "%"
|
||||
|
||||
|
||||
|
||||
>> energy ball ascii shader
|
||||
|
||||
d = dist(x/2+w/4, y, w/2, h/2)
|
||||
an = angle(x/2+w/4, y, w/2,h/2)+t/4200
|
||||
r=10.2
|
||||
|
||||
if (d < r) lex.bg = randint(r)
|
||||
|
||||
ll=abs(an|0)+""
|
||||
lex.char=ll[ll.length-1]
|
||||
|
||||
if (d > r) {
|
||||
lex.bg = randint(d)
|
||||
lex.fg = randint(d)
|
||||
lex.char = ll[ll.length-2]
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> drifting fire
|
||||
|
||||
t += sin(x/1000)*100000
|
||||
pos = y/h*6 + sin(x*3) - cos(y*t/10000-10)
|
||||
pos = clamp(pos, 0, 6)
|
||||
lex.bg = hue(pos)
|
||||
|
||||
|
||||
|
||||
>> the "bJoel56" shader
|
||||
|
||||
yy=y
|
||||
x-=w/2
|
||||
y-=h/2
|
||||
|
||||
lex.bg = blue(yy/h+random())
|
||||
lex.fg = green(yy/h*4 + sin(x/100+random()/2)) // hue(t/1000)|0;
|
||||
|
||||
var abcd=".'~:;!>+=icjtJYSGSXDQKHNWM";
|
||||
function chara (aa,n) { return aa[clamp(n*aa.length, 0, aa.length)|0] }
|
||||
lex.char = chara(abcd, y/h*(5/3 + tan(x/100)+random()/1))
|
||||
|
||||
|
||||
|
||||
>> frog shader v2
|
||||
|
||||
t/=-100
|
||||
d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t)/2 ) * 10
|
||||
|
||||
lex.char=',./>"ASE$#'[(floor(d))]
|
||||
lex.fg = [1,3,9][floor(d*3)%3]
|
||||
lex.bg=1
|
||||
|
||||
|
||||
|
||||
>> frog shader v3
|
||||
|
||||
// set period to like 0.2 for a normal circle
|
||||
period = y/10
|
||||
|
||||
t/=-100
|
||||
d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t) * period )
|
||||
dd = d * 10.5
|
||||
d3 = dd < 8 ? 0 : 1
|
||||
|
||||
lex.char=' .,"+/>OXEN'[(floor(dd))]
|
||||
lex.fg = [3,9][floor(d3)]
|
||||
lex.bg=1
|
||||
|
||||
|
||||
|
||||
>> spaceships
|
||||
|
||||
many cool shaders are possible with this technique.. changing the char
|
||||
gradient (lex.char=...) etc. i love how the dots move on v4.
|
||||
|
||||
this is a variation that looks like a bunch of ships flying across the screen.
|
||||
has a really cool 3d look to it cuz the rows move at different speeds.
|
||||
|
||||
period = sin(y)
|
||||
|
||||
t/=-100
|
||||
d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t) * period )
|
||||
dd = d * 10.5
|
||||
d3 = dd < 8 ? 0 : 1
|
||||
|
||||
lex.char=' .,"+/>\^+'[(floor(dd))]
|
||||
lex.fg = [3,9][floor(d3)]
|
||||
lex.bg=1
|
||||
|
||||
|
||||
|
||||
>> concentric circles with a wavy "sunburst" pattern going around them
|
||||
|
||||
x -= w/2
|
||||
y -= h/2
|
||||
|
||||
x /= h
|
||||
y /= h/2 + 2
|
||||
|
||||
r = dist(x,y,0,0)
|
||||
ang = angle(x,y,0,0)
|
||||
|
||||
if (r < 0.6) {
|
||||
if (abs(mod(sin((r*t)/100000000000) + ang*18,TWO_PI)) < 2)
|
||||
lex.bg = 12
|
||||
else
|
||||
lex.bg = 5
|
||||
}
|
||||
else if (r < 0.65)
|
||||
lex.bg = 4
|
||||
else if (abs(mod(sin((r*t)/100000000000) + ang*18,TWO_PI)) < 2)
|
||||
lex.bg = 7
|
||||
else
|
||||
lex.bg = 8
|
||||
|
||||
|
||||
|
||||
>> slash-based interference patterns
|
||||
|
||||
if (x > h*2) x=h-x
|
||||
y-=h/2
|
||||
t/=2000
|
||||
|
||||
if (sin(x-y*t) > 0) {
|
||||
lex.bg=1
|
||||
lex.fg=4
|
||||
lex.char= Math.floor(x-y*10001+t)%2 ? '\/' : '\\'
|
||||
}
|
||||
else {
|
||||
lex.bg=1
|
||||
lex.fg=9
|
||||
lex.char= Math.floor(3*x+y+t)%2 ? '\\' : ' '
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> sparkling stars
|
||||
|
||||
if (lex.char != " ") {
|
||||
lex.fg =floor( Math.random()*10 )
|
||||
var az="Xx+*"
|
||||
lex.char=az[floor(az.indexOf(lex.char)+ t/10000000 +Math.random())%az.length]
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> coogi x/y doodle
|
||||
|
||||
xx=x
|
||||
t/=1000
|
||||
x/=w/2
|
||||
y/=h/2
|
||||
y-=1
|
||||
x-=1
|
||||
x*=x-sin(y/t)
|
||||
y*=1
|
||||
|
||||
lex.bg = 1 // gray( sin(x/(y/3-1)+t) + sin(y/4+t) )
|
||||
lex.fg = hue( sin((y/5)+t) - cos(x*t) *5 )
|
||||
lex.char = " _.,:;\"~| "[Math.round(xx*(y+1+(x+t/102)/4)*13)%13]
|
||||
|
||||
|
||||
|
||||
>> glitch shader - produces odd combinations of fg/bg
|
||||
|
||||
lex.char=String.fromCharCode(lex.char.charCodeAt(0)+1)
|
||||
lex.bg+=7
|
||||
lex.fg+=5
|
||||
|
||||
|
||||
|
||||
>> dots / lines shader
|
||||
|
||||
xx = ((t/10*x)*y/10)%8
|
||||
lex.bg = colors.black
|
||||
lex.fg = green(x*3+y*5)
|
||||
lex.char = ((xx%1) !== 0) ? " " : " .,;=+!@"[xx]
|
||||
|
||||
|
||||
|
||||
>> munching squares horizon
|
||||
|
||||
t/=100
|
||||
y+=10
|
||||
x-=w/2
|
||||
x/=y/10
|
||||
lex.bg=hue((x^y)+t)
|
||||
|
||||
|
||||
|
||||
>> grayscale vertical interlacing
|
||||
|
||||
First, make a canvas that's totally white.
|
||||
|
||||
Run this shader:
|
||||
|
||||
if (lex.bg == 0) {
|
||||
lex.bg = ((x)%2) ? 15 : 14
|
||||
}
|
||||
|
||||
Then set your brush to a white square.
|
||||
|
||||
Run this shader w/ animate:
|
||||
|
||||
if (lex.bg == 0) {
|
||||
lex.bg = ((x)%2) ? 0 : 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
>> nice purple/orange texture
|
||||
|
||||
lex.bg=colors.purple
|
||||
lex.fg=colors.orange
|
||||
x/=3
|
||||
x=floor(x+y/2.1) // <- this is cool number to change
|
||||
if (x+10*sin(x)+10*cos(y/(t%100)) < y/3) {
|
||||
lex.char="abcdefghijklmnopqrstuvwxyz"[x%26]
|
||||
} else {
|
||||
lex.char="abcdefghijklmnopqrstuvwxyz".toUpperCase()[x%26]
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
SHADER UTILITIES
|
||||
================
|
||||
|
||||
These are little snippets which may be useful in writing your own shaders.
|
||||
|
||||
|
||||
|
||||
>> basic way to slow the frame rate of a shader.
|
||||
|
||||
window.zz=window.zz||0
|
||||
if(!(x+y)) zz++
|
||||
if (lex.bg != 1 && !(zz % 4)) {
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
>> handy for brushes - use color to mask brush shape
|
||||
|
||||
lex.opacity = lex.bg == colors.black ? 0 : 1
|
||||
|
||||
|
||||
Tip: Set to "animate brush", then use option+shift (alt+shift) to
|
||||
copy color from the canvas. Brush will have the "shape" of the
|
||||
copied color only. Can be a cool effect when used with fg/bg only.
|
||||
|
||||
|
||||
|
||||
>> copy color from canvas at x/y
|
||||
|
||||
lex.assign( canvas.get(x,y) )
|
||||
|
||||
|
||||
|
||||
>> animate canvas up and to the left..
|
||||
|
||||
lex.assign( canvas.get(x+1,y+1) )
|
||||
|
||||
|
@ -0,0 +1,156 @@
|
||||
ASCII SHADER TUTORIAL
|
||||
=====================
|
||||
|
||||
In the asdf.us/ascii shaders, you write a little math function that executes on every
|
||||
pixel on the selected area. The shaders can affect either the brush, the selected region,
|
||||
or the whole canvas.
|
||||
|
||||
Shaders can also be animated, so they update live. With a shader applied to the brush,
|
||||
the brush changes continuously as you draw.
|
||||
|
||||
|
||||
THE LEX OBJECT
|
||||
==============
|
||||
|
||||
Essentially you are writing a Javascript function that modifies this "lex" object, which
|
||||
has four properties
|
||||
|
||||
1) lex.bg = this is the background color
|
||||
2) lex.fg = this is the foreground color (text color)
|
||||
3) lex.char = this is the letter that you see in the space
|
||||
4) lex.opacity = this is whether the pixel actually draws or not
|
||||
- so like a circular brush is opacity 1 in the middle and opacity 0 on the corners
|
||||
|
||||
|
||||
THE COLOR CODE NUMBERS
|
||||
======================
|
||||
|
||||
With lex.bg and lex.fg, the goal is to have a number between 0 and 15, corresponding to
|
||||
the color code values from mIRC.
|
||||
|
||||
If you shift-click on the color palette, you can cycle it around to the one which shows
|
||||
the actual order of the mIRC colors.
|
||||
|
||||
The mIRC colors are the ones that go white, black, dark blue, green, red, dark red ...
|
||||
and these correspond to the numbers 0, 1, 2, 3, 4 ...
|
||||
|
||||
|
||||
COLOR CYCLING
|
||||
=============
|
||||
|
||||
Additionally there are some color functions that might help -
|
||||
These functions make it easier to cycle through colors in a way that makes sense logically
|
||||
(since the mIRC colors are in a weird order)
|
||||
|
||||
- hue(...) = this creates a cycle of colors in terms of their hue or color name,
|
||||
so you get a rainbow that goes from dark red through yellow, green, blue,
|
||||
purple, and back
|
||||
- gray(...) = cycles through grayscale
|
||||
- red(...) yellow(...) green(...) blue(...) purple(...) = use smaller palettes
|
||||
- inv_hue(...) fire(...) dark_gray(...) = these are oddities i made for fun
|
||||
|
||||
|
||||
VARIABLES
|
||||
=========
|
||||
|
||||
Variables you have at your disposal are similar to the asdf.us/shader tool -
|
||||
|
||||
- x, y = the coordinates of the pixel
|
||||
- mouse.x, mouse.y = the coordinate of the mouse as it hovers over the canvas
|
||||
- t = the current time, in milliseconds
|
||||
|
||||
TIP: The time will increase very quickly - it's good to add t /= 1000 at the top of
|
||||
your shader so it goes slowly (and won't cause a seizure).
|
||||
|
||||
|
||||
FUNCTIONS
|
||||
=========
|
||||
|
||||
Remember, this is Javascript. You have the basic operators:
|
||||
|
||||
+ - / *
|
||||
|
||||
And the bitwise operators:
|
||||
|
||||
& | ^ ~
|
||||
|
||||
You can do if statements with the standard comparison operators:
|
||||
|
||||
< > == <= >=
|
||||
|
||||
You also have access to all the functions on the Math object:
|
||||
|
||||
floor, ceil, round
|
||||
abs, sign, mod(n,m), xor
|
||||
pow, exp, sqrt
|
||||
cos, sin, tan
|
||||
acos, asin, atan, atan2
|
||||
random() rand(n) randint(n) randrange(a,b)
|
||||
E, PI, PHI
|
||||
|
||||
And some utility functions which might help:
|
||||
|
||||
clamp(n,min,max)
|
||||
mix(n,a,b) (lerp)
|
||||
step(a,b)
|
||||
smoothstep(min,max,n)
|
||||
avg(m,n,a)
|
||||
cosp, sinp (mapped to [0,1])
|
||||
pixel(x,y) == 4*(y*w+h)
|
||||
dist(x,y,a,b)
|
||||
angle(x,y,a,b)
|
||||
choice(array)
|
||||
deg(radians), rad(degrees)
|
||||
|
||||
|
||||
BEYOND BASIC COLORS
|
||||
===================
|
||||
|
||||
Other weird effects are possible if you combine these color functions.
|
||||
|
||||
For instance, if you do hue(x+y) you'll get a rainbow. But remember, this is just
|
||||
outputting a number between 0 and 15. So you can do hue(x+y) + 1 and get a different
|
||||
cycle which does not really have anything to do with the rainbow, but looks cool.
|
||||
|
||||
|
||||
HOW DRAWING WORKS IN THE ASCII TOOL
|
||||
===================================
|
||||
|
||||
When you click and drag to draw a line, your mouse produces a series of points which
|
||||
describe the line you tried to draw. But these points do not necessarily make a
|
||||
continuous line - more like a series of dots, which it then draw lines between to make
|
||||
a "line" or "brush stroke".
|
||||
|
||||
A line between two points is made by stamping the brush at regular intervals between the
|
||||
points which, with these brushes, ends up filling the space in between so it looks like
|
||||
you drew a continuous line.
|
||||
|
||||
This is why when you draw a line with a big brush, it smears the outer edges.. The stamps
|
||||
happen right next to each other, so you wind up seeing mostly brush edges.
|
||||
|
||||
You can visualize this effect with the following shader:
|
||||
|
||||
lex.bg = mouse.x + mouse.y
|
||||
|
||||
Drawing strokes quickly, or slowly.
|
||||
Make sure to make it animate to brush.
|
||||
Results could look like this:
|
||||
|
||||
http://i.asdf.us/im/f9/1458658781640-ascii-bamboo.png
|
||||
|
||||
|
||||
SAMPLE SHADERS
|
||||
==============
|
||||
|
||||
You can see a list of example shaders here:
|
||||
|
||||
http://asdf.us/ascii/doc/shaderz.txt
|
||||
|
||||
If you make a cool shader and want to see it on the list, please get in touch!
|
||||
You can find me on irc.jollo.org:9999 (ssl) in #sally, making color codes with my friends.
|
||||
|
||||
Thanks and have fun!
|
||||
|
||||
~ Bamboo, 22 Marzo 2016
|
||||
|
||||
|
@ -0,0 +1,16 @@
|
||||
asdf.us/ascii tips
|
||||
==================
|
||||
|
||||
These keyboard commands work in brush mode (square, circle, cross):
|
||||
|
||||
[ brush smaller
|
||||
] brush bigger
|
||||
ctrl~click on brush erase cell
|
||||
ctrl~click on canvas draw with bg color
|
||||
shift~click on canvas draw line from last position
|
||||
alt~click on canvas fill brush with sampled color
|
||||
alt~shift~click on canvas copy canvas to brush
|
||||
rightclick on palette set bg color (when drawing with a letter)
|
||||
|
||||
h/t timb for guide
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,99 @@
|
||||
|
||||
var dragging = false
|
||||
var drawing = false
|
||||
var erasing = false
|
||||
var selecting = false
|
||||
var filling = false
|
||||
var changed = false
|
||||
var transforming = false
|
||||
var mirror_x = false
|
||||
var mirror_y = false
|
||||
var focused
|
||||
|
||||
var canvas, tools, palette, controls, brush, mode
|
||||
var current_tool, current_filetool, current_canvas
|
||||
var mouse = { x: 0, y: 0 }
|
||||
|
||||
function init () {
|
||||
build()
|
||||
bind()
|
||||
}
|
||||
function build () {
|
||||
shader.init()
|
||||
// shader.run(canvas)
|
||||
shader.animate()
|
||||
|
||||
canvas.append(canvas_rapper)
|
||||
brush.append(brush_rapper)
|
||||
palette.append(palette_rapper)
|
||||
letters.append(letters_rapper)
|
||||
letters.repaint("Basic Latin")
|
||||
|
||||
controls.circle.focus()
|
||||
// controls.shader.focus()
|
||||
|
||||
brush.bg = colors.red
|
||||
brush.generate()
|
||||
brush.build()
|
||||
|
||||
// controls.grid.use()
|
||||
canvas.resize_rapper()
|
||||
}
|
||||
function bind () {
|
||||
canvas.bind()
|
||||
palette.bind()
|
||||
letters.bind()
|
||||
brush.bind()
|
||||
controls.bind()
|
||||
keys.bind()
|
||||
clipboard.bind()
|
||||
|
||||
window.addEventListener('mouseup', function(e){
|
||||
dragging = erasing = false
|
||||
// if (current_filetool.name != 'shader' && current_filetool.name != 'load' && current_filetool.name != 'save' && is_desktop) {
|
||||
// cursor_input.focus()
|
||||
// }
|
||||
|
||||
var ae = document.activeElement
|
||||
|
||||
if (ae !== shader_textarea && ae !== import_textarea) {
|
||||
if (is_desktop) cursor_input.focus()
|
||||
}
|
||||
|
||||
if (selecting) {
|
||||
selection.up(e)
|
||||
}
|
||||
else if (transforming) {
|
||||
transform.up(e)
|
||||
}
|
||||
})
|
||||
window.addEventListener("touchend", function(){
|
||||
if (current_tool.name === "text") {
|
||||
if (is_desktop) cursor_input.focus()
|
||||
}
|
||||
dragging = false
|
||||
})
|
||||
|
||||
window.addEventListener('mousedown', function(e){
|
||||
// if (current_filetool.name != 'shader' && is_desktop) { cursor_input.focus() }
|
||||
})
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
if (is_desktop) { cursor_input.focus() }
|
||||
document.body.classList.remove('loading')
|
||||
})
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
// if (changed && !in_iframe()) return "You have edited this drawing."
|
||||
}
|
||||
|
||||
function in_iframe () {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
@ -0,0 +1,212 @@
|
||||
var clipboard = (function () {
|
||||
|
||||
var exports = {
|
||||
format: "irssi",
|
||||
importing: false,
|
||||
visible: false,
|
||||
canvas: document.createElement("canvas"),
|
||||
canvas_r: document.createElement("canvas"),
|
||||
|
||||
bind: function () {
|
||||
import_button.addEventListener("click", exports.import_colorcode)
|
||||
import_textarea.addEventListener("focus", exports.focus)
|
||||
import_textarea.addEventListener("blur", exports.blur)
|
||||
import_textarea.addEventListener('paste', exports.paste)
|
||||
},
|
||||
setFormat: function (name) {
|
||||
return function () {
|
||||
clipboard.format = name
|
||||
if (! clipboard.importing) { clipboard.export_data() }
|
||||
}
|
||||
},
|
||||
show: function () { import_rapper.style.display = "block"; clipboard.visible = true; changed = false },
|
||||
hide: function () { import_rapper.style.display = "none"; clipboard.visible = false },
|
||||
focus: function () {
|
||||
if (! clipboard.importing) {
|
||||
import_textarea.focus()
|
||||
import_textarea.select()
|
||||
}
|
||||
},
|
||||
blur: function () {
|
||||
},
|
||||
|
||||
import_mode: function () {
|
||||
focus()
|
||||
clipboard.importing = true
|
||||
format_el.style.display = 'none'
|
||||
cutoff_warning_el.style.display = 'none'
|
||||
import_buttons.style.display = "inline"
|
||||
import_textarea.value = ""
|
||||
},
|
||||
|
||||
export_mode: function () {
|
||||
focus()
|
||||
clipboard.importing = false
|
||||
import_buttons.style.display = "none"
|
||||
format_el.style.display = 'inline'
|
||||
cutoff_warning_el.style.display = 'none'
|
||||
clipboard.export_data()
|
||||
},
|
||||
|
||||
paste: function (e) {
|
||||
e.preventDefault()
|
||||
// images will come through as files
|
||||
var types = toArray(e.clipboardData.types)
|
||||
import_textarea.value = ""
|
||||
types.forEach(function(type, i){
|
||||
console.log(type)
|
||||
// this can be text/plain or text/html..
|
||||
if (type.match('text/plain')) {
|
||||
import_textarea.value = e.clipboardData.getData(type)
|
||||
}
|
||||
else {
|
||||
console.error("unknown type!", item.type)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
import_colorcode: function (data, no_undo) {
|
||||
if (data && data.preventDefault) {
|
||||
data = import_textarea.value
|
||||
}
|
||||
else {
|
||||
data = data || import_textarea.value
|
||||
}
|
||||
|
||||
var irssi_style_regex = /^\s*\/exec -out printf ("%b" )?"/;
|
||||
|
||||
// turn irssi style into mirc style
|
||||
if (data.match(irssi_style_regex)){
|
||||
data = data.replace(/\\x03/gm, '\x03')
|
||||
.replace(/(\\x..)+/gm, unicode.unescapeFromEscapedBytes)
|
||||
.replace(/\\x5C/g, '\\')
|
||||
.replace(/\\n/gm, '\n')
|
||||
.replace(/\\`/gm, '`')
|
||||
.replace(/\\"/gm, '"')
|
||||
.replace(/\\\$/gm, '$')
|
||||
.replace(irssi_style_regex, '')
|
||||
.replace(/"\s*$/, '')
|
||||
}
|
||||
|
||||
var to_json = function(string, opts){
|
||||
var lines_in = string.split(/\r?\n/)
|
||||
var lines_out = []
|
||||
var w = 0, h = 0
|
||||
for (var y = 0; y < lines_in.length; y++) {
|
||||
var bg = 1, fg = 15
|
||||
var cells = [], line = lines_in[y]
|
||||
if (line.length === 0) {
|
||||
continue
|
||||
} else {
|
||||
for (var x = 0; x < line.length; x++) {
|
||||
switch (line[x]) {
|
||||
case "\x02": // ^B (unimplemented)
|
||||
break
|
||||
case "\x03": // ^C
|
||||
var parseColour = function(line, x) {
|
||||
if (/1[0-5]/.test(line.substr(x, 2))) {
|
||||
colour = parseInt(line.substr(x, 2))
|
||||
return [colour, x + 2]
|
||||
} else if (/0[0-9]/.test(line.substr(x, 2))) {
|
||||
colour = parseInt(line.substr(x, 2))
|
||||
return [colour, x + 2]
|
||||
} else if (/[0-9]/.test(line.substr(x, 1))) {
|
||||
colour = parseInt(line.substr(x, 1))
|
||||
return [colour, x + 1]
|
||||
} else {
|
||||
return [undefined, x]
|
||||
}
|
||||
}
|
||||
var bg_ = undefined, fg_ = undefined, x_ = x + 1;
|
||||
[fg_, x_] = parseColour(line, x_)
|
||||
if (line[x_] === ",") {
|
||||
[bg_, x_] = parseColour(line, x_ + 1)
|
||||
}
|
||||
if ((bg_ == undefined) && (fg_ == undefined)) {
|
||||
[bg, fg] = [1, 15]
|
||||
} else {
|
||||
bg = (bg_ != undefined) ? bg_ : bg;
|
||||
fg = (fg_ != undefined) ? fg_ : fg;
|
||||
};
|
||||
if (x_ != x) {x = x_ - 1}; break;
|
||||
case "\x06": // ^F (unimplemented)
|
||||
break
|
||||
case "\x0f": // ^O
|
||||
[bg, fg] = [1, 15]; break;
|
||||
case "\x16": // ^V
|
||||
[bg, fg] = [fg, bg]; break;
|
||||
case "\x1f": // ^_ (unimplemented)
|
||||
break
|
||||
default:
|
||||
cells.push({bg: bg, fg: fg, value: line[x]})
|
||||
}
|
||||
}
|
||||
if (cells.length > 0) {
|
||||
if (w < cells.length) {
|
||||
w = cells.length
|
||||
}
|
||||
lines_out.push(cells); h++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {h: h, lines: lines_out, w: w}
|
||||
}
|
||||
var json = to_json(data, {fg:0, bg:1})
|
||||
|
||||
if (!no_undo) undo.new()
|
||||
if (!no_undo) undo.save_rect(0,0, canvas.w, canvas.h)
|
||||
if (json.w !== canvas.w || json.h !== canvas.h){
|
||||
if (!no_undo) undo.save_size(canvas.w, canvas.h)
|
||||
canvas.resize(json.w, json.h, true)
|
||||
}
|
||||
canvas.clear()
|
||||
|
||||
for (var y = 0, line; line = json.lines[y]; y++){
|
||||
var row = canvas.aa[y]
|
||||
for (var x = 0, char; char = line[x]; x++){
|
||||
var lex = row[x]
|
||||
lex.char = char.value
|
||||
lex.fg = char.fg
|
||||
lex.bg = char.bg
|
||||
lex.opacity = 1
|
||||
lex.build()
|
||||
}
|
||||
}
|
||||
|
||||
current_filetool && current_filetool.blur()
|
||||
},
|
||||
|
||||
export_data: function () {
|
||||
var output
|
||||
// switch (clipboard.format) {
|
||||
switch (controls.save_format.value) {
|
||||
case 'ascii':
|
||||
output = canvas.ascii()
|
||||
break
|
||||
case 'mirc':
|
||||
output = canvas.mirc({cutoff: 425})
|
||||
break
|
||||
case 'irssi':
|
||||
output = canvas.irssi({cutoff: 425})
|
||||
break
|
||||
case 'ansi':
|
||||
output = canvas.ansi()
|
||||
break
|
||||
}
|
||||
if (output.cutoff){
|
||||
cutoff_warning_el.style.display = 'block'
|
||||
} else {
|
||||
cutoff_warning_el.style.display = 'none'
|
||||
}
|
||||
import_textarea.value = output
|
||||
clipboard.focus()
|
||||
return output
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
return exports
|
||||
|
||||
})()
|
||||
|
||||
|
@ -0,0 +1,106 @@
|
||||
|
||||
var fillColor = 1 // black
|
||||
|
||||
var color_names = ("white black dark-blue green red dark-red purple orange " +
|
||||
"yellow lime teal cyan blue magenta dark-gray light-gray").split(" ");
|
||||
|
||||
var all_color_hue_order = "dark-red red orange yellow lime green teal cyan blue dark-blue purple magenta black dark-gray light-gray white".split(" ");
|
||||
var all_color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green white light-gray dark-gray black".split(" ");
|
||||
var color_hue_order = "dark-red red orange yellow lime cyan teal blue dark-blue purple magenta".split(" ");
|
||||
var color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green".split(" ");
|
||||
var gray_names = ("black dark-gray light-gray white").split(" ")
|
||||
|
||||
var fire_names = ("black dark-red red orange yellow white cyan").split(" ")
|
||||
var red_names = ("black dark-red red").split(" ")
|
||||
var yellow_names = ("black orange yellow white").split(" ")
|
||||
var green_names = ("teal green lime").split(" ")
|
||||
var blue_names = ("black dark-blue blue").split(" ")
|
||||
var purple_names = ("dark-blue purple magenta red").split(" ")
|
||||
var dark_gray_names = ("black dark-blue teal dark-gray light-gray white").split(" ")
|
||||
var color_alphabet = "abcdefghijklmnop";
|
||||
var colors = {}
|
||||
color_names.forEach(function(name, i){
|
||||
colors[name.replace("-", "")] = i
|
||||
colors[name] = i
|
||||
})
|
||||
colors.brown = 5
|
||||
|
||||
function get_inverse (n) { return colors[all_color_inv_order.indexOf(color_names[n])] }
|
||||
|
||||
function mirc_color (n) { return mod(n, 16)|0 }
|
||||
function mirc_color_reverse (n) { return mod(-(n+1), 16)|0 }
|
||||
function all_hue (n) { return colors[all_color_hue_order[mod(n, 16)|0]] }
|
||||
function all_inv_hue (n) { return colors[all_color_inv_order[mod(n, 16)|0]] }
|
||||
function hue (n) { return colors[color_hue_order[mod(n, 11)|0]] }
|
||||
function rand_hue () { return colors[color_hue_order[randint(11)]] }
|
||||
function rand_gray () { return colors[gray_names[randint(4)]] }
|
||||
function inv_hue (n) { return colors[color_inv_order[mod(n, 11)|0]] }
|
||||
function gray (n) { return colors[gray_names[mod(n, 4)|0]] }
|
||||
function fire (n) { return colors[fire_names[mod(n, 7)|0]] }
|
||||
function red (n) { return colors[red_names[mod(n, 3)|0]] }
|
||||
function yellow (n) { return colors[yellow_names[mod(n, 4)|0]] }
|
||||
function green (n) { return colors[green_names[mod(n, 3)|0]] }
|
||||
function blue (n) { return colors[blue_names[mod(n, 3)|0]] }
|
||||
function purple (n) { return colors[purple_names[mod(n, 4)|0]] }
|
||||
function dark_gray (n) { return colors[dark_gray_names[mod(n, 4)|0]] }
|
||||
|
||||
var css_lookup = {
|
||||
'rgb(255, 255, 255)': 'A',
|
||||
'rgb(0, 0, 0)': 'B',
|
||||
'rgb(0, 0, 127)': 'C',
|
||||
'rgb(0, 147, 0)': 'D',
|
||||
'red': 'E',
|
||||
'rgb(127, 0, 0)': 'F',
|
||||
'rgb(156, 0, 156)': 'G',
|
||||
'rgb(252, 127, 0)': 'H',
|
||||
'rgb(255, 255, 0)': 'I',
|
||||
'rgb(0, 252, 0)': 'J',
|
||||
'rgb(0, 147, 147)': 'K',
|
||||
'rgb(0, 255, 255)': 'L',
|
||||
'rgb(0, 0, 252)': 'M',
|
||||
'rgb(255, 0, 255)': 'N',
|
||||
'rgb(127, 127, 127)': 'O',
|
||||
'rgb(210, 210, 210)': 'P',
|
||||
};
|
||||
var css_reverse_lookup = {}
|
||||
Object.keys(css_lookup).forEach(function(color){
|
||||
css_reverse_lookup[ css_lookup[color].charCodeAt(0) - 65 ] = color
|
||||
})
|
||||
|
||||
var ansi_fg = [
|
||||
97, // white
|
||||
30, // black
|
||||
34, // dark blue
|
||||
32, // green
|
||||
91, // light red
|
||||
31, // dark red
|
||||
35, // purple
|
||||
33, // "dark yellow" (orange?)
|
||||
93, // "light yellow"
|
||||
92, // light green
|
||||
36, // cyan (teal?)
|
||||
96, // light cyan
|
||||
94, // light blue
|
||||
95, // light magenta
|
||||
90, // dark gray
|
||||
37, // light gray
|
||||
]
|
||||
|
||||
var ansi_bg = [
|
||||
107, // white
|
||||
40, // black
|
||||
44, // dark blue
|
||||
42, // green
|
||||
101, // light red
|
||||
41, // dark red
|
||||
45, // purple
|
||||
43, // yellow (orange)
|
||||
103, // light yellow
|
||||
102, // light green
|
||||
46, // cyan (teal?)
|
||||
106, // light cyan
|
||||
104, // light blue
|
||||
105, // light magenta
|
||||
100, // dark gray
|
||||
47, // light gray
|
||||
]
|
@ -0,0 +1,896 @@
|
||||
var nopaint = (function(){
|
||||
|
||||
controls.no = new Tool (nopaint_no_el)
|
||||
controls.no.use = function(state){
|
||||
undo.undo()
|
||||
controls.paint.focus()
|
||||
}
|
||||
controls.no.context = function(e){
|
||||
e.preventDefault()
|
||||
nopaint.turbo()
|
||||
}
|
||||
|
||||
controls.paint = new Tool (nopaint_paint_el)
|
||||
controls.paint.use = function(state){
|
||||
nopaint.paint()
|
||||
nopaint_pause_el.classList.toggle("hidden", false)
|
||||
focused = controls.paint.lex
|
||||
}
|
||||
controls.paint.context = function(e){
|
||||
e.preventDefault()
|
||||
nopaint.autoplay()
|
||||
}
|
||||
|
||||
controls.nopaint_pause = new Tool (nopaint_pause_el)
|
||||
controls.nopaint_pause.use = function(state){
|
||||
// nopaint.pause()
|
||||
nopaint.autoplay(false)
|
||||
nopaint_pause_el.classList.toggle("hidden", true)
|
||||
focused = canvas.aa[0][0]
|
||||
}
|
||||
|
||||
// use own stepwise clock to drive tweens
|
||||
oktween.raf = function(){}
|
||||
|
||||
var nopaint = {}
|
||||
nopaint.debug = true
|
||||
nopaint.delay = nopaint.normal_delay = 100
|
||||
nopaint.turbo_delay = 0
|
||||
nopaint.tool = null
|
||||
nopaint.tools = {}
|
||||
nopaint.keys = []
|
||||
nopaint.weights = []
|
||||
nopaint.step = 0
|
||||
nopaint.time = 0
|
||||
nopaint.timeout = false
|
||||
nopaint.toggle = function(state){
|
||||
var state = typeof state == "boolean" ? state : nopaint_rapper.classList.contains("hidden")
|
||||
nopaint_rapper.classList.toggle("hidden", ! state)
|
||||
nopaint_pause_el.classList.toggle("hidden", true)
|
||||
document.body.classList.toggle("nopaint", state)
|
||||
return state
|
||||
}
|
||||
nopaint.no = function(){
|
||||
undo.undo()
|
||||
nopaint.paint()
|
||||
}
|
||||
nopaint.raw_key = controls.paint.lex.raw_key = keys.left_right_key(function(n){
|
||||
if (! nopaint.timeout) return
|
||||
if (n < 0) nopaint.no()
|
||||
else if (n > 0) nopaint.paint()
|
||||
else nopaint.pause()
|
||||
})
|
||||
nopaint.pause = nopaint.blur = function(){
|
||||
clearTimeout(nopaint.timeout)
|
||||
nopaint.timeout = 0
|
||||
nopaint.step = 0
|
||||
}
|
||||
nopaint.paint = function(){
|
||||
var state = undo.new()
|
||||
delete state.focus
|
||||
nopaint.pause()
|
||||
nopaint.switch_tool()
|
||||
nopaint.go()
|
||||
}
|
||||
nopaint.go = function(){
|
||||
nopaint.timeout = setTimeout(nopaint.go, nopaint.delay)
|
||||
oktween.update(nopaint.time)
|
||||
nopaint.tool.paint( nopaint.step )
|
||||
nopaint.time += 1
|
||||
nopaint.step += 1
|
||||
}
|
||||
nopaint.switch_tool = function(){
|
||||
last_tool = nopaint.tool
|
||||
last_tool && last_tool.finish()
|
||||
nopaint.tool = nopaint.get_random_tool( last_tool )
|
||||
nopaint.tool.start( last_tool )
|
||||
nopaint.debug && console.log("> %s", nopaint.tool.type)
|
||||
}
|
||||
nopaint.add_tool = function(fn){
|
||||
nopaint.tools[fn.type] = fn
|
||||
}
|
||||
nopaint.disable_all_tools = function(){
|
||||
Object.keys(nopaint.tools).forEach(function(key){
|
||||
nopaint.tools[key].disabled = true
|
||||
})
|
||||
}
|
||||
nopaint.enable_tools = function(keys){
|
||||
keys.forEach(function(key){
|
||||
if (nopaint.tools[key]) nopaint.tools[key].disabled = false
|
||||
})
|
||||
}
|
||||
nopaint.get_random_tool = function( last_tool ){
|
||||
var n = rand( nopaint.sum )
|
||||
for (var i = 0, _len = nopaint.weights.length; i < _len; i++) {
|
||||
if (n < nopaint.weights[i] && (! last_tool || nopaint.keys[i] !== last_tool.key)) {
|
||||
return nopaint.tools[ nopaint.keys[i] ]
|
||||
}
|
||||
}
|
||||
return nopaint.tools[ choice(nopaint.keys) ]
|
||||
}
|
||||
nopaint.regenerate_weights = function(){
|
||||
nopaint.sum = 0
|
||||
nopaint.weights = []
|
||||
nopaint.keys = Object.keys( nopaint.tools ).sort(function(a,b){
|
||||
return nopaint.tools[b].opt.weight-nopaint.tools[a].opt.weight
|
||||
}).filter(function(key){
|
||||
return ! nopaint.tools[key].disabled
|
||||
})
|
||||
nopaint.keys.forEach(function(key){
|
||||
nopaint.sum += nopaint.tools[key].opt.weight
|
||||
nopaint.weights.push( nopaint.sum )
|
||||
})
|
||||
}
|
||||
|
||||
nopaint.is_turbo = false
|
||||
nopaint.turbo = function(state){
|
||||
nopaint.is_turbo = typeof state == "boolean" ? state : ! nopaint.is_turbo
|
||||
nopaint.delay = nopaint.is_turbo ? nopaint.turbo_delay : nopaint.normal_delay
|
||||
if (nopaint.is_turbo) {
|
||||
nopaint_no_el.classList.add("locked")
|
||||
}
|
||||
else {
|
||||
nopaint_no_el.classList.remove("locked")
|
||||
}
|
||||
}
|
||||
|
||||
nopaint.is_autoplay = false
|
||||
nopaint.autoplay = function(state){
|
||||
nopaint.is_autoplay = typeof state == "boolean" ? state : ! nopaint.is_autoplay
|
||||
if (nopaint.is_autoplay) {
|
||||
nopaint_paint_el.classList.add("locked")
|
||||
if (! nopaint.player) {
|
||||
nopaint.player = new RandomPlayer ()
|
||||
}
|
||||
if (! nopaint.timeout) nopaint.paint()
|
||||
nopaint.player.play()
|
||||
}
|
||||
else {
|
||||
nopaint_paint_el.classList.remove("locked")
|
||||
nopaint.pause()
|
||||
nopaint.player && nopaint.player.pause()
|
||||
}
|
||||
}
|
||||
|
||||
var NopaintPlayer = Model({
|
||||
type: "player",
|
||||
upload_png: false,
|
||||
upload_interval: 100,
|
||||
step: 0,
|
||||
timeout: null,
|
||||
delay: function(){
|
||||
return nopaint.is_turbo ? randrange(150, 300) : randrange(400, 800)
|
||||
},
|
||||
reset: function(){
|
||||
this.no_count = 0
|
||||
this.paint_count = 0
|
||||
},
|
||||
pause: function(){
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = 0
|
||||
},
|
||||
play: function(){
|
||||
clearTimeout(this.timeout)
|
||||
var delay = this.delay()
|
||||
this.timeout = setTimeout(this.play.bind(this), delay)
|
||||
this.check_fitness()
|
||||
this.step += 1
|
||||
},
|
||||
check_fitness: function(){
|
||||
switch (this.fitness()) {
|
||||
case "no":
|
||||
nopaint.no_count += 1
|
||||
nopaint.since_last_no = 0
|
||||
nopaint.since_last_paint += 1
|
||||
nopaint.no()
|
||||
break
|
||||
case "paint":
|
||||
nopaint.paint_count += 1
|
||||
nopaint.since_last_no += 1
|
||||
nopaint.since_last_paint = 0
|
||||
nopaint.paint()
|
||||
break
|
||||
case "screenshot":
|
||||
if (this.save_as_png) break
|
||||
console.log("uploading...")
|
||||
setTimeout(clipboard.upload_png, 0)
|
||||
// fall thru
|
||||
default:
|
||||
nopaint.since_last_no += 1
|
||||
nopaint.since_last_paint += 1
|
||||
break
|
||||
}
|
||||
},
|
||||
fitness: function(){},
|
||||
})
|
||||
|
||||
var RandomPlayer = NopaintPlayer.extend({
|
||||
type: "random_player",
|
||||
upload_png: false,
|
||||
fitness: function(){
|
||||
var no_prob = random()
|
||||
var paint_prob = 1 - no_prob
|
||||
if (paint_prob < 0.3) {
|
||||
return "paint"
|
||||
}
|
||||
else if (no_prob < 0.5) {
|
||||
return "no"
|
||||
}
|
||||
else if ( this.paint_count > 100 && (this.step % 100) == 99 ) {
|
||||
return "screenshot"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var StylePlayer = NopaintPlayer.extend({
|
||||
type: "style_player",
|
||||
upload_png: false,
|
||||
fitness: function(){
|
||||
var no_prob = random()
|
||||
var paint_prob = 1 - no_prob
|
||||
var np, pp
|
||||
var steps = this.since_last_paint
|
||||
|
||||
if (nopaint.tool.is_brush) {
|
||||
if (nopaint.tool.is_clone) {
|
||||
if (steps < randrange(3,8)) return
|
||||
np = 0.3
|
||||
pp = 0.4
|
||||
}
|
||||
else if (nopaint.tool.is_erase) {
|
||||
if (steps < randrange(2,6)) return
|
||||
np = 0.3
|
||||
pp = 0.4
|
||||
}
|
||||
else {
|
||||
if (steps < randrange(2,4)) return
|
||||
np = 0.1
|
||||
pp = 0.3
|
||||
}
|
||||
}
|
||||
if (nopaint.tool.is_shader) {
|
||||
switch (nopaint.tool.name) {
|
||||
case "rotate":
|
||||
case "scale":
|
||||
if (steps < randrange(2,4)) return
|
||||
np = 0.1
|
||||
pp = 0.2
|
||||
break
|
||||
default:
|
||||
np = 0.2
|
||||
pp = 0.2
|
||||
}
|
||||
}
|
||||
if (nopaint.tool.is_fill) {
|
||||
np = 0.4
|
||||
pp = 0.1
|
||||
}
|
||||
|
||||
if (steps > 10) {
|
||||
np *= 0.7
|
||||
pp *= 1.5
|
||||
|
||||
if (nopaint.is_turbo) {
|
||||
np *= 1.2
|
||||
pp *= 1.2
|
||||
}
|
||||
}
|
||||
|
||||
if (paint_prob < np) {
|
||||
return "paint"
|
||||
}
|
||||
else if (no_prob < np) {
|
||||
return "no"
|
||||
}
|
||||
else if ( this.paint_count > 100 && (this.step % 100) == 99 ) {
|
||||
return "screenshot"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/* Base models for brushes */
|
||||
|
||||
var NopaintTool = Model({
|
||||
type: "none",
|
||||
init: function(opt){
|
||||
this.opt = opt || {}
|
||||
},
|
||||
start: function(){},
|
||||
paint: function(t){},
|
||||
update: function(t){},
|
||||
finish: function(){},
|
||||
})
|
||||
|
||||
var NopaintBrush = NopaintTool.extend({
|
||||
type: "brush",
|
||||
is_brush: true,
|
||||
init: function(opt){
|
||||
this.opt = opt || {}
|
||||
this.opt.max_radius = this.opt.max_radius || 10
|
||||
this.p = {x: randint(canvas.w), y: randint(canvas.h)}
|
||||
this.fg = 0
|
||||
this.bg = 1
|
||||
this.char = " "
|
||||
this.tweens = []
|
||||
},
|
||||
|
||||
start: function(last_brush){
|
||||
this.set_brush_mask()
|
||||
this.toggle_channels()
|
||||
this.reset( last_brush )
|
||||
this.regenerate()
|
||||
draw.down({}, null, [this.p.x, this.p.y])
|
||||
},
|
||||
|
||||
paint: function(t){
|
||||
this.update(t)
|
||||
draw.move_toroidal({}, null, [this.p.x, this.p.y])
|
||||
},
|
||||
|
||||
finish: function(){
|
||||
this.tweens.forEach(function(t){ t.cancel() })
|
||||
this.tweens = []
|
||||
},
|
||||
|
||||
reorient: function(last_brush){
|
||||
var a = {}, b
|
||||
|
||||
if (last_brush) {
|
||||
this.p.x = a.x = randint(canvas.w)
|
||||
this.p.y = a.y = randint(canvas.h)
|
||||
}
|
||||
else {
|
||||
a.x = this.p.x
|
||||
a.y = this.p.y
|
||||
}
|
||||
|
||||
b = this.get_next_point()
|
||||
|
||||
var tween = oktween.add({
|
||||
obj: this.p,
|
||||
from: a,
|
||||
to: b,
|
||||
duration: b.duration,
|
||||
easing: b.easing,
|
||||
update: b.update,
|
||||
finished: function(){
|
||||
this.iterate()
|
||||
this.regenerate()
|
||||
}.bind(this)
|
||||
})
|
||||
this.tweens.push(tween)
|
||||
},
|
||||
|
||||
get_next_point: function(){
|
||||
var radius = randrange(2, this.opt.max_radius)
|
||||
var b = {}
|
||||
b.duration = randrange(1, 7)
|
||||
b.easing = choice(easings)
|
||||
b.x = this.p.x + randrange(-radius, radius)
|
||||
b.y = this.p.y + randrange(-radius, radius)
|
||||
return b
|
||||
},
|
||||
|
||||
set_brush_mask: function(){
|
||||
var r = Math.random()
|
||||
if (r < 0.2) {
|
||||
brush.mask = blit.square
|
||||
}
|
||||
else if (r < 0.6) {
|
||||
brush.mask = blit.circle
|
||||
}
|
||||
else if (r < 0.9) {
|
||||
brush.mask = blit.cross
|
||||
}
|
||||
else{
|
||||
brush.mask = blit.inverted_cross
|
||||
}
|
||||
},
|
||||
|
||||
toggle_channels: function(){
|
||||
if (Math.random() < 0.001) { controls.bg.use(false) }
|
||||
else if (! brush.draw_bg && Math.random() < 0.25) { controls.bg.use(true) }
|
||||
|
||||
if (Math.random() < 0.1) { controls.fg.use(false) }
|
||||
else if (! brush.draw_fg && Math.random() < 0.5) { controls.fg.use(true) }
|
||||
|
||||
if (Math.random() < 0.02) { controls.char.use(false) }
|
||||
else if (! brush.draw_char && Math.random() < 0.2) { controls.char.use(true) }
|
||||
},
|
||||
|
||||
iterate: function( last_brush ){
|
||||
this.reorient( last_brush )
|
||||
},
|
||||
|
||||
regenerate: function(){
|
||||
brush.load( this )
|
||||
brush.generate()
|
||||
}
|
||||
})
|
||||
|
||||
var easings = "linear circ_out circ_in circ_in_out quad_in quad_out quad_in_out".split(" ")
|
||||
|
||||
/* Standard brushes */
|
||||
|
||||
var SolidBrush = NopaintBrush.extend({
|
||||
type: "solid",
|
||||
|
||||
recolor: function(){
|
||||
this.fg = this.bg = randint(16)
|
||||
this.char = " "
|
||||
},
|
||||
|
||||
resize: function(m,n){
|
||||
m = m || 3
|
||||
n = n || 0
|
||||
var bw = xrandrange(5, 0, m) + n
|
||||
brush.resize( round(bw * randrange(0.9, 1.8)) || 1, round(bw) || 1 )
|
||||
},
|
||||
|
||||
reset: function( last_brush ){
|
||||
this.opt.max_radius = randrange(5,20)
|
||||
this.resize()
|
||||
this.reorient( last_brush )
|
||||
this.recolor( last_brush )
|
||||
this.regenerate()
|
||||
},
|
||||
|
||||
iterate: function( last_brush ){
|
||||
this.resize()
|
||||
this.reorient( last_brush )
|
||||
},
|
||||
})
|
||||
|
||||
var EraseBrush = SolidBrush.extend({
|
||||
type: "erase",
|
||||
reset: function( last_brush ){
|
||||
this.opt.max_radius = randrange(8, 20)
|
||||
this.reorient( last_brush )
|
||||
this.bg = random() < 0.2 ? colors.white : colors.black
|
||||
this.char = " "
|
||||
brush.load( this )
|
||||
this.resize(3,2)
|
||||
},
|
||||
})
|
||||
|
||||
var ShadowBrush = NopaintBrush.extend({
|
||||
type: "shadow",
|
||||
pairs: [
|
||||
[ colors.yellow, colors.orange ],
|
||||
[ colors.orange, colors.darkred ],
|
||||
[ colors.red, colors.darkred ],
|
||||
[ colors.lime, colors.green ],
|
||||
[ colors.cyan, colors.teal ],
|
||||
[ colors.cyan, colors.blue ],
|
||||
[ colors.blue, colors.darkblue ],
|
||||
[ colors.magenta, colors.purple ],
|
||||
[ colors.lightgray, colors.darkgray ],
|
||||
[ colors.darkgray, colors.black ],
|
||||
[ colors.white, colors.lightgray ],
|
||||
[ colors.white, colors.black ],
|
||||
],
|
||||
shapes: [
|
||||
[[0],[1]],
|
||||
[[0,0],[1,1]],
|
||||
[[1,0,0],[1,1,1]],
|
||||
[[0,0,1],[1,1,1]],
|
||||
[[0,0,0],[1,1,1]],
|
||||
[[0,0,0,0],[1,1,1,1]],
|
||||
[[1,0,0,0],[null,1,1,1]],
|
||||
[[0,0,0,1],[1,1,1,null]],
|
||||
[[0,0],[1,0],[1,1]],
|
||||
[[0,0],[0,1],[1,1]],
|
||||
],
|
||||
reset: function( last_brush ){
|
||||
var pair = choice(this.pairs)
|
||||
var shape = choice(this.shapes)
|
||||
this.reorient( last_brush )
|
||||
brush.char = " "
|
||||
brush.resize(shape[0].length, shape.length)
|
||||
brush.generate()
|
||||
brush.rebuild()
|
||||
brush.forEach(function(lex,x,y){
|
||||
if (shape[y][x] == null) {
|
||||
lex.opacity = 0
|
||||
}
|
||||
else {
|
||||
lex.fg = lex.bg = pair[ shape[y][x] ]
|
||||
lex.opacity = 1
|
||||
}
|
||||
lex.build()
|
||||
})
|
||||
},
|
||||
regenerate: function(){},
|
||||
})
|
||||
|
||||
var RandomBrush = SolidBrush.extend({
|
||||
type: "random",
|
||||
iterate: function( last_brush ){
|
||||
this.reorient( last_brush )
|
||||
this.recolor( last_brush )
|
||||
},
|
||||
})
|
||||
|
||||
var HueBrush = SolidBrush.extend({
|
||||
type: "hue",
|
||||
recolor: function(){
|
||||
this.fg = this.bg = rand_hue()
|
||||
this.char = " "
|
||||
},
|
||||
})
|
||||
|
||||
var GrayBrush = SolidBrush.extend({
|
||||
type: "gray",
|
||||
recolor: function(){
|
||||
this.fg = this.bg = rand_gray()
|
||||
this.char = " "
|
||||
},
|
||||
})
|
||||
|
||||
var LetterBrush = SolidBrush.extend({
|
||||
type: "letter",
|
||||
recolor: function(){
|
||||
this.fg = rand_hue()
|
||||
this.bg = rand_hue()
|
||||
this.char = choice( unicode.block(letters.charset, 32) )
|
||||
},
|
||||
})
|
||||
|
||||
var RandomLetterBrush = LetterBrush.extend({
|
||||
type: "random-letter",
|
||||
iterate: function(){
|
||||
if (Math.random() < 0.01) {
|
||||
this.fg += 1
|
||||
}
|
||||
if (Math.random() < 0.05) {
|
||||
var n = this.fg
|
||||
this.fg = this.bg
|
||||
this.bg = n
|
||||
}
|
||||
if (Math.random() < 0.7) {
|
||||
this.char = choice( unicode.block(letters.charset, 32) )
|
||||
}
|
||||
this.regenerate()
|
||||
this.__iterate()
|
||||
},
|
||||
update: function(){
|
||||
if (Math.random() < 0.3) {
|
||||
this.char = choice( unicode.block(letters.charset, 32) )
|
||||
}
|
||||
this.regenerate()
|
||||
},
|
||||
})
|
||||
|
||||
var CloneBrush = SolidBrush.extend({
|
||||
type: "clone",
|
||||
|
||||
is_clone: true,
|
||||
|
||||
reset: function( last_brush ){
|
||||
this.opt.max_radius = randrange(5, 20)
|
||||
this.reorient( last_brush )
|
||||
this.resize(4,2)
|
||||
this.clone_random_region()
|
||||
},
|
||||
|
||||
clone_random_region: function(x, y){
|
||||
var x = randrange(0, canvas.w - brush.w)
|
||||
var y = randrange(0, canvas.h - brush.h)
|
||||
this.clone_region(x, y)
|
||||
},
|
||||
|
||||
clone_region: function(x, y){
|
||||
blit.copy_toroidal_from(canvas, brush, round(x-brush.w/2), round(y-brush.h/2))
|
||||
brush.mask(brush)
|
||||
},
|
||||
|
||||
iterate: function( last_brush ){
|
||||
this.reorient( last_brush )
|
||||
},
|
||||
|
||||
regenerate: function(){},
|
||||
})
|
||||
|
||||
var SmearBrush = CloneBrush.extend({
|
||||
type: "smear",
|
||||
|
||||
update: function(){
|
||||
var r = random()
|
||||
var jitter_x = randnullsign() * xrand(2, 2)
|
||||
var jitter_y = randnullsign() * xrand(2, 2)
|
||||
this.clone_region( this.p.x + jitter_x, this.p.y + jitter_y )
|
||||
},
|
||||
|
||||
iterate: function( last_brush ){
|
||||
this.resize(4, 2)
|
||||
this.update()
|
||||
this.reorient( last_brush )
|
||||
}
|
||||
})
|
||||
|
||||
var StarsTool = NopaintBrush.extend({
|
||||
type: "stars",
|
||||
chars: "....,,'''*",
|
||||
|
||||
start: function(last_brush){
|
||||
this.reorient( last_brush )
|
||||
},
|
||||
|
||||
paint: function(t){
|
||||
if (Math.random() < 0.5) {
|
||||
var lex = canvas.get(this.p.x, this.p.y)
|
||||
undo.save_lex(lex.x, lex.y, lex)
|
||||
lex.fg = rand_hue()
|
||||
// lex.bg = colors.black
|
||||
lex.char = choice(this.chars)
|
||||
lex.build()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
/* Fill tool */
|
||||
|
||||
var FillTool = NopaintTool.extend({
|
||||
type: "fill",
|
||||
rate: 25,
|
||||
is_fill: true,
|
||||
start: function(){
|
||||
this.fill()
|
||||
},
|
||||
paint: function(t){
|
||||
if ((t % this.rate) == this.rate-1) {
|
||||
this.fill()
|
||||
}
|
||||
},
|
||||
recolor: function(){
|
||||
this.fg = this.bg = randint(16)
|
||||
this.char = " "
|
||||
this.opacity = 1
|
||||
},
|
||||
fill: function(){
|
||||
var x = randint(canvas.w)
|
||||
var y = randint(canvas.h)
|
||||
this.recolor()
|
||||
draw.fill(this, x, y)
|
||||
}
|
||||
})
|
||||
|
||||
var FillLetterTool = FillTool.extend({
|
||||
type: "fill-letter",
|
||||
rate: 25,
|
||||
recolor: function(){
|
||||
this.fg = randint(16)
|
||||
this.bg = randint(16)
|
||||
this.char = choice( unicode.block(letters.charset, 32) )
|
||||
this.opacity = 1
|
||||
},
|
||||
})
|
||||
|
||||
/* Shader Tools */
|
||||
|
||||
var ShaderTool = NopaintTool.extend({
|
||||
type: "shader",
|
||||
speed: 3,
|
||||
is_shader: true,
|
||||
is_recursive: false,
|
||||
start: function(){
|
||||
undo.save_rect(0, 0, canvas.w, canvas.h)
|
||||
this.canvas = canvas.clone()
|
||||
},
|
||||
paint: function(t){
|
||||
if ((t % this.speed) == 0) {
|
||||
var w = canvas.w
|
||||
var h = canvas.h
|
||||
var lex
|
||||
if (this.is_recursive) {
|
||||
this.canvas.assign(canvas)
|
||||
}
|
||||
this.before_shade()
|
||||
for (var x = 0; x < w; x++) {
|
||||
for (var y = 0; y < h; y++) {
|
||||
lex = canvas.get(x, y)
|
||||
if (! this.shade( this.canvas, canvas, lex, x, y, w, h )) {
|
||||
lex.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
before_shade: function(){},
|
||||
shade: function(src, dest, lex, x, y, w, h){},
|
||||
finish: function(){
|
||||
this.canvas.demolish()
|
||||
}
|
||||
})
|
||||
|
||||
var ColorizeTool = ShaderTool.extend({
|
||||
type: "colorize",
|
||||
fns: [mirc_color_reverse,hue,inv_hue,gray,fire,red,yellow,green,blue,purple,dark_gray],
|
||||
speed: 5,
|
||||
start: function(){
|
||||
this.__start()
|
||||
this.i = randint(this.fns.length)
|
||||
},
|
||||
before_shade: function(){
|
||||
this.i = (this.i + 1) % this.fns.length
|
||||
this.fn = this.fns[this.i]
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
lex.bg = this.fn( lex.bg )
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
var TranslateTool = ShaderTool.extend({
|
||||
type: "translate",
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
speed: 3,
|
||||
start: function(){
|
||||
this.__start()
|
||||
this.dx = randint(3)-1
|
||||
this.dy = randint(3)-1
|
||||
this.x = this.y = 0
|
||||
if (! this.dx && ! this.dy) {
|
||||
this.dx = 1
|
||||
this.dy = 0
|
||||
}
|
||||
},
|
||||
before_shade: function(){
|
||||
this.x += this.dx
|
||||
this.y += this.dy
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
var copy = src.get(x+this.x, y+this.y)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
var SliceTool = ShaderTool.extend({
|
||||
type: "slice",
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
speed: 1,
|
||||
is_recursive: true,
|
||||
start: function(){
|
||||
this.__start()
|
||||
this.is_y = Math.random() > 0.3
|
||||
this.limit = this.is_y ? canvas.h : canvas.w
|
||||
this.position = randint(this.limit)
|
||||
this.direction = 1
|
||||
},
|
||||
before_shade: function(){
|
||||
if (Math.random() < 0.6) {
|
||||
this.position = mod(this.position + 1, this.limit)
|
||||
}
|
||||
if (Math.random() > 0.8) {
|
||||
this.direction = randsign()
|
||||
}
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
if (this.is_y) {
|
||||
if (y >= this.position) {
|
||||
var copy = src.get(x + this.direction, y)
|
||||
lex.assign(copy)
|
||||
}
|
||||
}
|
||||
else if (x >= this.position) {
|
||||
var copy = src.get(x, y + this.direction)
|
||||
lex.assign(copy)
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
var ScaleTool = ShaderTool.extend({
|
||||
type: "scale",
|
||||
scale: 1,
|
||||
dscale: 0,
|
||||
speed: 3,
|
||||
start: function(){
|
||||
this.__start()
|
||||
var sign = randsign()
|
||||
this.x_scale = 1
|
||||
this.y_scale = 1
|
||||
this.dx_scale = randsign() * randrange(0.0005, 0.01)
|
||||
var r = Math.random()
|
||||
if (r < 0.333) {
|
||||
this.dy_scale = this.dx_scale * randrange(0.85, 1.25)
|
||||
}
|
||||
else if (r < 0.666) {
|
||||
this.dy_scale = this.dx_scale
|
||||
}
|
||||
else {
|
||||
this.dy_scale = randsign() * randrange(0.0005, 0.01)
|
||||
}
|
||||
},
|
||||
before_shade: function(){
|
||||
this.x_scale += this.dx_scale
|
||||
this.y_scale += this.dy_scale
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
x = (x/w) * 2 - 1
|
||||
y = (y/h) * 2 - 1
|
||||
x *= this.x_scale
|
||||
y *= this.y_scale
|
||||
x = (x + 1) / 2 * w
|
||||
y = (y + 1) / 2 * h
|
||||
var copy = src.get(x, y)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
var RotateTool = ShaderTool.extend({
|
||||
type: "rotate",
|
||||
theta: 0,
|
||||
d_theta: 0,
|
||||
|
||||
start: function(){
|
||||
this.__start()
|
||||
var sign = randsign()
|
||||
this.theta = 0
|
||||
this.d_theta = randsign() * randrange(0.001, 0.05)
|
||||
},
|
||||
before_shade: function(){
|
||||
this.theta += this.d_theta
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
x = (x/w) * 2 - 1
|
||||
y = (y/h) * 2 - 1
|
||||
var ca = cos(this.theta)
|
||||
var sa = sin(this.theta)
|
||||
var a = x * ca - y * sa
|
||||
var b = x * sa + y * ca
|
||||
x = (a + 1) / 2 * w
|
||||
y = (b + 1) / 2 * h
|
||||
var copy = src.get(x, y)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
var CycleTool = ShaderTool.extend({
|
||||
type: "cycle",
|
||||
n: 0,
|
||||
speed: 5,
|
||||
is_recursive: true,
|
||||
start: function(){
|
||||
this.__start()
|
||||
this.n = randsign()
|
||||
if (random() < 0.2) this.n *= randint(15)
|
||||
},
|
||||
shade: function(src, dest, lex, x, y){
|
||||
lex.bg += this.n
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
nopaint.add_tool( new SolidBrush({ weight: 5 }) )
|
||||
nopaint.add_tool( new ShadowBrush({ weight: 10 }) )
|
||||
nopaint.add_tool( new EraseBrush({ weight: 5 }) )
|
||||
nopaint.add_tool( new RandomBrush({ weight: 4 }) )
|
||||
nopaint.add_tool( new HueBrush({ weight: 5 }) )
|
||||
nopaint.add_tool( new GrayBrush({ weight: 5 }) )
|
||||
nopaint.add_tool( new LetterBrush({ weight: 2 }) )
|
||||
nopaint.add_tool( new RandomLetterBrush({ weight: 12 }) )
|
||||
nopaint.add_tool( new CloneBrush({ weight: 8 }) )
|
||||
nopaint.add_tool( new SmearBrush({ weight: 10 }) )
|
||||
nopaint.add_tool( new FillTool({ weight: 3 }) )
|
||||
nopaint.add_tool( new FillLetterTool({ weight: 6 }) )
|
||||
nopaint.add_tool( new StarsTool({ weight: 2 }) )
|
||||
nopaint.add_tool( new TranslateTool({ weight: 4 }) )
|
||||
nopaint.add_tool( new CycleTool({ weight: 1 }) )
|
||||
nopaint.add_tool( new ScaleTool({ weight: 3 }) )
|
||||
nopaint.add_tool( new RotateTool({ weight: 3 }) )
|
||||
nopaint.add_tool( new SliceTool({ weight: 4 }) )
|
||||
nopaint.add_tool( new ColorizeTool({ weight: 1 }) )
|
||||
nopaint.regenerate_weights()
|
||||
|
||||
nopaint.toggle(true)
|
||||
|
||||
nopaint.player = new StylePlayer ()
|
||||
|
||||
return nopaint
|
||||
})()
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
oktween.add({
|
||||
obj: el.style,
|
||||
units: "px",
|
||||
from: { left: 0 },
|
||||
to: { left: 100 },
|
||||
duration: 1000,
|
||||
easing: oktween.easing.circ_out,
|
||||
update: function(obj){
|
||||
console.log(obj.left)
|
||||
}
|
||||
finished: function(){
|
||||
console.log("done")
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
var oktween = (function(){
|
||||
var oktween = {}
|
||||
var tweens = oktween.tweens = []
|
||||
var last_t = 0
|
||||
var id = 0
|
||||
oktween.speed = 1
|
||||
oktween.raf = requestAnimationFrame
|
||||
oktween.add = function(tween){
|
||||
tween.id = id++
|
||||
tween.obj = tween.obj || {}
|
||||
if (tween.easing) {
|
||||
if (typeof tween.easing == "string") {
|
||||
tween.easing = oktween.easing[tween.easing]
|
||||
}
|
||||
}
|
||||
else {
|
||||
tween.easing = oktween.easing.linear
|
||||
}
|
||||
if (! ('from' in tween) && ! ('to' in tween)) {
|
||||
tween.keys = []
|
||||
}
|
||||
else if (! ('from' in tween) ) {
|
||||
tween.from = {}
|
||||
tween.keys = Object.keys(tween.to)
|
||||
tween.keys.forEach(function(prop){
|
||||
tween.from[prop] = parseFloat(tween.obj[prop])
|
||||
})
|
||||
}
|
||||
else {
|
||||
tween.keys = Object.keys(tween.from)
|
||||
}
|
||||
tween.delay = tween.delay || 0
|
||||
tween.start = last_t + tween.delay
|
||||
tween.done = false
|
||||
tween.after = tween.after || []
|
||||
tween.then = function(fn){ tween.after.push(fn); return tween }
|
||||
tween.cancel = function(){
|
||||
var index = tweens.indexOf(tween)
|
||||
if (index != -1) tweens.splice(index, 1)
|
||||
tween.obj = null
|
||||
tween.after = null
|
||||
tween.done = null
|
||||
}
|
||||
tween.tick = 0
|
||||
tween.skip = tween.skip || 1
|
||||
tween.dt = 0
|
||||
tweens.push(tween)
|
||||
return tween
|
||||
}
|
||||
oktween.update = function(t) {
|
||||
oktween.raf(oktween.update)
|
||||
last_t = t * oktween.speed
|
||||
if (tweens.length == 0) return
|
||||
var done = false
|
||||
tweens.forEach(function(tween, i){
|
||||
var dt = Math.min(1.0, (t - tween.start) / tween.duration)
|
||||
tween.tick++
|
||||
if (dt < 0 || (dt < 1 && (tween.tick % tween.skip != 0))) return
|
||||
var ddt = tween.dt = tween.easing(dt)
|
||||
tween.keys.forEach(function(prop){
|
||||
val = lerp( ddt, tween.from[prop], tween.to[prop] )
|
||||
if (tween.round) val = Math.round(val)
|
||||
if (tween.units) val = (Math.round(val)) + tween.units
|
||||
tween.obj[prop] = val
|
||||
})
|
||||
tween.update && tween.update(tween.obj, dt)
|
||||
if (dt == 1) {
|
||||
tween.finished && tween.finished(tween)
|
||||
if (tween.after.length) {
|
||||
var twn = tween.after.shift()
|
||||
twn.obj = twn.obj || tween.obj
|
||||
twn.after = tween.after
|
||||
oktween.add(twn)
|
||||
}
|
||||
if (tween.loop) {
|
||||
tween.start = t + tween.delay
|
||||
}
|
||||
else {
|
||||
done = tween.done = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if (done) {
|
||||
tweens = tweens.filter(function(tween){ return ! tween.done })
|
||||
}
|
||||
}
|
||||
function lerp(n,a,b){ return (b-a)*n+a }
|
||||
|
||||
// requestAnimationFrame(oktween.update)
|
||||
|
||||
oktween.easing = {
|
||||
linear: function(t){
|
||||
return t
|
||||
},
|
||||
circ_out: function(t) {
|
||||
return Math.sqrt(1 - (t = t - 1) * t)
|
||||
},
|
||||
circ_in: function(t){
|
||||
return -(Math.sqrt(1 - (t * t)) - 1)
|
||||
},
|
||||
circ_in_out: function(t) {
|
||||
return ((t*=2) < 1) ? -0.5 * (Math.sqrt(1 - t * t) - 1) : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1)
|
||||
},
|
||||
quad_in: function(n){
|
||||
return Math.pow(n, 2)
|
||||
},
|
||||
quad_out: function(n){
|
||||
return n * (n - 2) * -1
|
||||
},
|
||||
quad_in_out: function(n){
|
||||
n = n * 2
|
||||
if(n < 1){ return Math.pow(n, 2) / 2 }
|
||||
return -1 * ((--n) * (n - 2) - 1) / 2
|
||||
},
|
||||
cubic_bezier: function (mX1, mY1, mX2, mY2) {
|
||||
function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
|
||||
function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
|
||||
function C(aA1) { return 3.0 * aA1; }
|
||||
|
||||
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
||||
function CalcBezier(aT, aA1, aA2) {
|
||||
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
|
||||
}
|
||||
|
||||
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
|
||||
function GetSlope(aT, aA1, aA2) {
|
||||
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
|
||||
}
|
||||
|
||||
function GetTForX(aX) {
|
||||
// Newton raphson iteration
|
||||
var aGuessT = aX;
|
||||
for (var i = 0; i < 10; ++i) {
|
||||
var currentSlope = GetSlope(aGuessT, mX1, mX2);
|
||||
if (currentSlope == 0.0) return aGuessT;
|
||||
var currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
|
||||
aGuessT -= currentX / currentSlope;
|
||||
}
|
||||
return aGuessT;
|
||||
}
|
||||
|
||||
return function(aX) {
|
||||
if (mX1 == mY1 && mX2 == mY2) return aX; // linear
|
||||
return CalcBezier(aX, mY1, mY2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return oktween
|
||||
})()
|
@ -0,0 +1,203 @@
|
||||
var unicode = (function(){
|
||||
var UNICODE_BLOCK_LIST = [
|
||||
0x0020, 0x007F, "Basic Latin",
|
||||
0x0080, 0x00FF, "Latin-1 Supplement",
|
||||
0x2500, 0x257F, "Box Drawing",
|
||||
0x2580, 0x259F, "Block Elements",
|
||||
]
|
||||
var UNICODE_BLOCK_COUNT = UNICODE_BLOCK_LIST.length / 3
|
||||
var UNICODE_LOOKUP = {}
|
||||
for (var i = 0, len = UNICODE_BLOCK_LIST.length; i < len; i += 3) {
|
||||
UNICODE_LOOKUP[ UNICODE_BLOCK_LIST[i+2] ] = [ UNICODE_BLOCK_LIST[i], UNICODE_BLOCK_LIST[i+1] ]
|
||||
}
|
||||
|
||||
function block (name, n){
|
||||
var b = UNICODE_LOOKUP[name]
|
||||
if (! b) return ""
|
||||
return range.apply(null, b).map(function(n){ return String.fromCharCode(n) })
|
||||
}
|
||||
function entities (a) {
|
||||
return a.map(function(k){ return "&#" + k.join(";&#") + ";" }).join("<br>")
|
||||
}
|
||||
function index (j) {
|
||||
return [ UNICODE_BLOCK_LIST[j*3], UNICODE_BLOCK_LIST[j*3+1], UNICODE_BLOCK_LIST[j*3+2], [] ]
|
||||
}
|
||||
function range(m,n){
|
||||
if (m > n) return []
|
||||
var a = new Array (n-m)
|
||||
for (var i = 0, j = m; j <= n; i++, j++) {
|
||||
a[i] = j
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => '\xE3\x81\x82\xE3\x81\x84'
|
||||
// [ 0343, 0201, 0202, 0343, 0201, 0204 ] => '\343\201\202\343\201\204'
|
||||
function convertBytesToEscapedString(data_bytes, base) {
|
||||
var escaped = '';
|
||||
for (var i = 0; i < data_bytes.length; ++i) {
|
||||
var prefix = (base == 16 ? "\\x" : "\\");
|
||||
var num_digits = base == 16 ? 2 : 3;
|
||||
var escaped_byte = prefix + formatNumber(data_bytes[i], base, num_digits)
|
||||
escaped += escaped_byte;
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
// r'\xE3\x81\x82\xE3\x81\x84' => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ]
|
||||
// r'\343\201\202\343\201\204' => [ 0343, 0201, 0202, 0343, 0201, 0204 ]
|
||||
function convertEscapedBytesToBytes(str) {
|
||||
var parts = str.split("\\x");
|
||||
parts.shift(); // Trim the first element.
|
||||
var codes = [];
|
||||
var max = Math.pow(2, 8);
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
var code = parseInt(parts[i], 16);
|
||||
if (code >= 0 && code < max) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
// Malformed code ignored.
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
// [ 0x3042, 0x3044 ] => "ã‚ã„"
|
||||
function convertUnicodeCodePointsToString(unicode_codes) {
|
||||
var utf16_codes = convertUnicodeCodePointsToUtf16Codes(unicode_codes);
|
||||
return convertUtf16CodesToString(utf16_codes);
|
||||
}
|
||||
// 0x3042 => [ 0xE3, 0x81, 0x82 ]
|
||||
function convertUnicodeCodePointToUtf8Bytes(unicode_code) {
|
||||
var utf8_bytes = [];
|
||||
if (unicode_code < 0x80) { // 1-byte
|
||||
utf8_bytes.push(unicode_code);
|
||||
} else if (unicode_code < (1 << 11)) { // 2-byte
|
||||
utf8_bytes.push((unicode_code >>> 6) | 0xC0);
|
||||
utf8_bytes.push((unicode_code & 0x3F) | 0x80);
|
||||
} else if (unicode_code < (1 << 16)) { // 3-byte
|
||||
utf8_bytes.push((unicode_code >>> 12) | 0xE0);
|
||||
utf8_bytes.push(((unicode_code >> 6) & 0x3f) | 0x80);
|
||||
utf8_bytes.push((unicode_code & 0x3F) | 0x80);
|
||||
} else if (unicode_code < (1 << 21)) { // 4-byte
|
||||
utf8_bytes.push((unicode_code >>> 18) | 0xF0);
|
||||
utf8_bytes.push(((unicode_code >> 12) & 0x3F) | 0x80);
|
||||
utf8_bytes.push(((unicode_code >> 6) & 0x3F) | 0x80);
|
||||
utf8_bytes.push((unicode_code & 0x3F) | 0x80);
|
||||
}
|
||||
return utf8_bytes;
|
||||
}
|
||||
// [ 0x3042, 0x3044 ] => [ 0x3042, 0x3044 ]
|
||||
// [ 0xD840, 0xDC0B ] => [ 0x2000B ] // A surrogate pair.
|
||||
function convertUnicodeCodePointsToUtf16Codes(unicode_codes) {
|
||||
var utf16_codes = [];
|
||||
for (var i = 0; i < unicode_codes.length; ++i) {
|
||||
var unicode_code = unicode_codes[i];
|
||||
if (unicode_code < (1 << 16)) {
|
||||
utf16_codes.push(unicode_code);
|
||||
} else {
|
||||
var first = ((unicode_code - (1 << 16)) / (1 << 10)) + 0xD800;
|
||||
var second = (unicode_code % (1 << 10)) + 0xDC00;
|
||||
utf16_codes.push(first)
|
||||
utf16_codes.push(second)
|
||||
}
|
||||
}
|
||||
return utf16_codes;
|
||||
}
|
||||
// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => [ 0x3042, 0x3044 ]
|
||||
function convertUtf8BytesToUnicodeCodePoints(utf8_bytes) {
|
||||
var unicode_codes = [];
|
||||
var unicode_code = 0;
|
||||
var num_followed = 0;
|
||||
for (var i = 0; i < utf8_bytes.length; ++i) {
|
||||
var utf8_byte = utf8_bytes[i];
|
||||
if (utf8_byte >= 0x100) {
|
||||
// Malformed utf8 byte ignored.
|
||||
} else if ((utf8_byte & 0xC0) == 0x80) {
|
||||
if (num_followed > 0) {
|
||||
unicode_code = (unicode_code << 6) | (utf8_byte & 0x3f);
|
||||
num_followed -= 1;
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
} else {
|
||||
if (num_followed == 0) {
|
||||
unicode_codes.push(unicode_code);
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
if (utf8_byte < 0x80){ // 1-byte
|
||||
unicode_code = utf8_byte;
|
||||
num_followed = 0;
|
||||
} else if ((utf8_byte & 0xE0) == 0xC0) { // 2-byte
|
||||
unicode_code = utf8_byte & 0x1f;
|
||||
num_followed = 1;
|
||||
} else if ((utf8_byte & 0xF0) == 0xE0) { // 3-byte
|
||||
unicode_code = utf8_byte & 0x0f;
|
||||
num_followed = 2;
|
||||
} else if ((utf8_byte & 0xF8) == 0xF0) { // 4-byte
|
||||
unicode_code = utf8_byte & 0x07;
|
||||
num_followed = 3;
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_followed == 0) {
|
||||
unicode_codes.push(unicode_code);
|
||||
} else {
|
||||
// Malformed UTF-8 sequence ignored.
|
||||
}
|
||||
unicode_codes.shift(); // Trim the first element.
|
||||
return unicode_codes;
|
||||
}
|
||||
// [ 0x3042, 0x3044 ] => "ã‚ã„"
|
||||
function convertUtf16CodesToString(utf16_codes) {
|
||||
var unescaped = '';
|
||||
for (var i = 0; i < utf16_codes.length; ++i) {
|
||||
unescaped += String.fromCharCode(utf16_codes[i]);
|
||||
}
|
||||
return unescaped;
|
||||
}
|
||||
// 0xff => "ff"
|
||||
// 0xff => "377"
|
||||
function formatNumber(number, base, num_digits) {
|
||||
var str = number.toString(base).toUpperCase();
|
||||
for (var i = str.length; i < num_digits; ++i) {
|
||||
str = "0" + str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// encodes unicode characters as escaped bytes - \xFF
|
||||
// encodes ONLY non-ascii characters
|
||||
function escapeToEscapedBytes (txt) {
|
||||
var escaped_txt = "", kode, utf8_bytes
|
||||
for (var i = 0; i < txt.length; i++) {
|
||||
kode = txt.charCodeAt(i)
|
||||
if (kode > 0x7f) {
|
||||
utf8_bytes = convertUnicodeCodePointToUtf8Bytes(kode)
|
||||
escaped_txt += convertBytesToEscapedString(utf8_bytes, 16)
|
||||
}
|
||||
else {
|
||||
escaped_txt += txt[i]
|
||||
}
|
||||
}
|
||||
return escaped_txt
|
||||
}
|
||||
|
||||
// convert \xFF\xFF\xFF to unicode
|
||||
function unescapeFromEscapedBytes (str) {
|
||||
var data_bytes = convertEscapedBytesToBytes(str);
|
||||
var unicode_codes = convertUtf8BytesToUnicodeCodePoints(data_bytes);
|
||||
return convertUnicodeCodePointsToString(unicode_codes);
|
||||
}
|
||||
|
||||
return {
|
||||
raw: UNICODE_BLOCK_LIST,
|
||||
lookup: UNICODE_LOOKUP,
|
||||
index: index,
|
||||
range: range,
|
||||
block: block,
|
||||
escapeToEscapedBytes: escapeToEscapedBytes,
|
||||
unescapeFromEscapedBytes: unescapeFromEscapedBytes,
|
||||
}
|
||||
})()
|
@ -0,0 +1,199 @@
|
||||
if (window.$) {
|
||||
$.fn.int = function(){ return parseInt($(this).val(),10) }
|
||||
$.fn.float = function(){ return parseFloat($(this).val()) }
|
||||
$.fn.string = function(){ return trim($(this).val()) }
|
||||
$.fn.enable = function() { return $(this).attr("disabled",null) }
|
||||
$.fn.disable = function() { return $(this).attr("disabled","disabled") }
|
||||
}
|
||||
|
||||
function noop(){}
|
||||
function trim(s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") }
|
||||
|
||||
var E = Math.E
|
||||
var PI = Math.PI
|
||||
var PHI = (1+Math.sqrt(5))/2
|
||||
var TWO_PI = PI*2
|
||||
var LN10 = Math.LN10
|
||||
function clamp(n,a,b){ return n<a?a:n<b?n:b }
|
||||
function norm(n,a,b){ return (n-a) / (b-a) }
|
||||
function lerp(n,a,b){ return (b-a)*n+a }
|
||||
function mix(n,a,b){ return a*(1-n)+b*n }
|
||||
function ceil(n){ return Math.ceil(n) }
|
||||
function floor(n){ return Math.floor(n) }
|
||||
function round(n){ return Math.round(n) }
|
||||
function max(a,b){ return Math.max(a,b) }
|
||||
function min(a,b){ return Math.min(a,b) }
|
||||
function abs(n){ return Math.abs(n) }
|
||||
function sign(n){ return Math.abs(n)/n }
|
||||
function pow(n,b) { return Math.pow(n,b) }
|
||||
function exp(n) { return Math.exp(n) }
|
||||
function log(n){ return Math.log(n) }
|
||||
function ln(n){ return Math.log(n)/LN10 }
|
||||
function sqrt(n) { return Math.sqrt(n) }
|
||||
function cos(n){ return Math.cos(n) }
|
||||
function sin(n){ return Math.sin(n) }
|
||||
function tan(n){ return Math.tan(n) }
|
||||
function acos(n){ return Math.cos(n) }
|
||||
function asin(n){ return Math.sin(n) }
|
||||
function atan(n){ return Math.atan(n) }
|
||||
function atan2(a,b){ return Math.atan2(a,b) }
|
||||
function sec(n){ return 1/cos(n) }
|
||||
function csc(n){ return 1/sin(n) }
|
||||
function cot(n){ return 1/tan(n) }
|
||||
function cosp(n){ return (1+Math.cos(n))/2 } // cos^2
|
||||
function sinp(n){ return (1+Math.sin(n))/2 }
|
||||
function random(){ return Math.random() }
|
||||
function rand(n){ return (Math.random()*n) }
|
||||
function randint(n){ return rand(n)|0 }
|
||||
function randrange(a,b){ return a + rand(b-a) }
|
||||
function randsign(){ return random() >= 0.5 ? -1 : 1 }
|
||||
function randnullsign(){ var r = random(); return r < 0.333 ? -1 : r < 0.666 ? 0 : 1 }
|
||||
|
||||
function xrandom(exp){ return Math.pow(Math.random(), exp) }
|
||||
function xrand(exp,n){ return (xrandom(exp)*n) }
|
||||
function xrandint(exp,n){ return rand(exp,n)|0 }
|
||||
function xrandrange(exp,a,b){ return a + xrand(exp,b-a) }
|
||||
|
||||
function choice(a){ return a[randint(a.length)] }
|
||||
function deg(n){ return n*180/PI }
|
||||
function rad(n){ return n*PI/180 }
|
||||
function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) }
|
||||
function mod(n,m){ n = n % m; return n < 0 ? (m + n) : n }
|
||||
function modi(n,m){ return floor(mod(n,m)) }
|
||||
function dist(x0,y0,x1,y1){ return sqrt(pow(x1-x0,2)+pow(y1-y0,2)) }
|
||||
function angle(x0,y0,x1,y1){ return atan2(y1-y0,x1-x0) }
|
||||
function avg(m,n,a){ return (m*(a-1)+n)/a }
|
||||
function quantize(a,b){ return floor(a/b)*b }
|
||||
function quantile(a,b){ return floor(a/b) }
|
||||
|
||||
function pixel(x,y){ return 4*(mod(y,actual_h)*actual_w+mod(x,actual_w)) }
|
||||
function rgbpixel(d,x,y){
|
||||
var p = pixel(~~x,~~y)
|
||||
r = d[p]
|
||||
g = d[p+1]
|
||||
b = d[p+2]
|
||||
a = d[p+3]
|
||||
}
|
||||
function fit(d,x,y){ rgbpixel(d,x*actual_w/w,y*actual_h/h) }
|
||||
|
||||
function step(a, b){
|
||||
return (b >= a) + 0
|
||||
// ^^ bool -> int
|
||||
}
|
||||
|
||||
function julestep (a,b,n) {
|
||||
return clamp(norm(n,a,b), 0.0, 1.0);
|
||||
}
|
||||
|
||||
// hermite curve apparently
|
||||
function smoothstep(min,max,n){
|
||||
var t = clamp((n - min) / (max - min), 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
function toArray(a){ return Array.prototype.slice.call(a) }
|
||||
function shuffle(a){
|
||||
for (var i = a.length; i > 0; i--){
|
||||
var r = randint(i)
|
||||
var swap = a[i-1]
|
||||
a[i-1] = a[r]
|
||||
a[r] = swap
|
||||
}
|
||||
return a
|
||||
}
|
||||
function reverse(a){
|
||||
var reversed = []
|
||||
for (var i = 0, _len = a.length-1; i <= _len; i++){
|
||||
reversed[i] = a[_len-i]
|
||||
}
|
||||
return reversed
|
||||
}
|
||||
function deinterlace(a){
|
||||
var odd = [], even = []
|
||||
for (var i = 0, _len = a.length; i < _len; i++) {
|
||||
if (i % 2) even.push(a[i])
|
||||
else odd.push(a[i])
|
||||
}
|
||||
return [even, odd]
|
||||
}
|
||||
function weave(a){
|
||||
var aa = deinterlace(a)
|
||||
var b = []
|
||||
aa[0].forEach(function(el){ b.push(el) })
|
||||
reverse(aa[1]).forEach(function(el){ b.push(el) })
|
||||
return b
|
||||
}
|
||||
function cssRule (selector, declaration) {
|
||||
var x = document.styleSheets, y = x.length-1;
|
||||
x[y].insertRule(selector+"{"+declaration+"}", x[y].cssRules.length);
|
||||
}
|
||||
|
||||
// easing functions
|
||||
function circular (t) { return Math.sqrt( 1 - ( --t * t ) ) }
|
||||
function quadratic (t) { return t * ( 2 - t ) }
|
||||
function back (t) {
|
||||
var b = 4;
|
||||
return ( t = t - 1 ) * t * ( ( b + 1 ) * t + b ) + 1;
|
||||
}
|
||||
function bounce (t) {
|
||||
if (t >= 1) return 1;
|
||||
if ( ( t /= 1 ) < ( 1 / 2.75 ) ) {
|
||||
return 7.5625 * t * t;
|
||||
} else if ( t < ( 2 / 2.75 ) ) {
|
||||
return 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75;
|
||||
} else if ( t < ( 2.5 / 2.75 ) ) {
|
||||
return 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375;
|
||||
} else {
|
||||
return 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375;
|
||||
}
|
||||
}
|
||||
function elastic (t) {
|
||||
var f = 0.22,
|
||||
e = 0.4;
|
||||
|
||||
if ( t === 0 ) { return 0; }
|
||||
if ( t == 1 ) { return 1; }
|
||||
|
||||
return ( e * Math.pow( 2, - 10 * t ) * Math.sin( ( t - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
|
||||
}
|
||||
|
||||
Model=function a(b,c,d,e){function f(){var a=this,f={};a.on=function(a,b){(f[a]||
|
||||
(f[a]=[])).push(b)},a.trigger=function(a,b){for(var c=f[a],d=0;c&&d<c.length;)c
|
||||
[d++](b)},a.off=function(a,b){for(d=f[a]||[];b&&(c=d.indexOf(b))>-1;)d.splice(c
|
||||
,1);f[a]=b?d:[]};for(c in b)d=b[c],a[c]=typeof d=="function"?function(){return(
|
||||
d=this.apply(a,arguments))===e?a:d}.bind(d):d;a.init&&a.init.apply(a,arguments)
|
||||
}return f.extend=function(f){d={};for(c in b)d[c]=b[c];for(c in f)d[c]=f[c],b[c
|
||||
]!==e&&(d["__"+c]=b[c]);return a(d)},f},typeof module=="object"&&(module.exports
|
||||
=Model); // c-{{{-<
|
||||
|
||||
function defaults (dest, src) {
|
||||
dest = dest || {}
|
||||
for (var i in src) {
|
||||
dest[i] = typeof dest[i] == 'undefined' ? src[i] : dest[i]
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
function setSelectionRange(input, selectionStart, selectionEnd) {
|
||||
if (input.setSelectionRange) {
|
||||
input.focus();
|
||||
input.setSelectionRange(selectionStart, selectionEnd);
|
||||
}
|
||||
else if (input.createTextRange) {
|
||||
var range = input.createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', selectionEnd);
|
||||
range.moveStart('character', selectionStart);
|
||||
range.select();
|
||||
}
|
||||
}
|
||||
function setCaretToPos(input, pos) {
|
||||
setSelectionRange(input, pos, pos);
|
||||
}
|
||||
|
||||
// Naive useragent detection pattern
|
||||
var is_iphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))
|
||||
var is_ipad = (navigator.userAgent.match(/iPad/i))
|
||||
var is_android = (navigator.userAgent.match(/Android/i))
|
||||
var is_mobile = is_iphone || is_ipad || is_android
|
||||
var is_desktop = ! is_mobile;
|
@ -0,0 +1,387 @@
|
||||
|
||||
var blit = (function(){
|
||||
var blit = {}
|
||||
blit.and = blit.atop = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell && lex.opacity > 0) {
|
||||
cell.assign(lex)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.or = blit.under = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell && cell.opacity == 0) {
|
||||
cell.assign(lex)
|
||||
}
|
||||
})
|
||||
}
|
||||
// copy the region of A beginning at x,y into B
|
||||
blit.copy_from = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell) {
|
||||
lex.assign(cell)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.copy_toroidal_from = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.get(u+x, v+y)
|
||||
if (cell) {
|
||||
lex.assign(cell)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.copy_to = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell) {
|
||||
cell.assign(lex)
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.invert = function(A, B, x, y){
|
||||
x = x || 0 ; y = y || 0
|
||||
B.forEach(function(lex, u, v){
|
||||
var cell = A.getCell(u+x, v+y)
|
||||
if (cell && lex.opacity > 0) {
|
||||
cell.fg = get_inverse(cell.fg)
|
||||
cell.bg = get_inverse(cell.bg)
|
||||
}
|
||||
})
|
||||
}
|
||||
var distance_rect = function(x, y, ratio){
|
||||
return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x, 2))
|
||||
}
|
||||
var distance_square = function(x, y, ratio){
|
||||
return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x * ratio, 2))
|
||||
}
|
||||
blit.circle = function(A, lex){
|
||||
var hw = brush.w/2, hh = brush.h/2
|
||||
var ratio, distance
|
||||
|
||||
if (brush.w === brush.h){
|
||||
distance = distance_square
|
||||
ratio = hw / hh * (brush.w === 3 || brush.w === 5 ? 1.2 : 1.05)
|
||||
} else {
|
||||
distance = distance_rect
|
||||
ratio = hw / hh
|
||||
}
|
||||
|
||||
A.forEach(function(lex,x,y) {
|
||||
if (distance(x - hw + 0.5, y - hh + 0.5, ratio) > hw){
|
||||
lex.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.cross = function(A, lex){
|
||||
A.forEach(function(lex,x,y) {
|
||||
if ((x+y)%2) {
|
||||
lex.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.inverted_cross = function(A, lex){
|
||||
// 1x1 brush should still draw something
|
||||
if (A.w == 1 && A.h == 1) {
|
||||
return
|
||||
}
|
||||
A.forEach(function(lex,x,y) {
|
||||
if (!((x+y)%2)) {
|
||||
lex.clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
blit.square = function(A, lex){
|
||||
// i.e. no transparency
|
||||
}
|
||||
return blit
|
||||
})()
|
||||
|
||||
var draw = (function(){
|
||||
|
||||
var last_point = [0,0]
|
||||
|
||||
function down (e, lex, point) {
|
||||
var w = canvas.w, h = canvas.h
|
||||
erasing = (e.which == "3" || e.ctrlKey)
|
||||
changed = true
|
||||
if (e.shiftKey) {
|
||||
line (lex, last_point, point, erasing)
|
||||
if (mirror_x) {
|
||||
line(lex, [w-last_point[0], last_point[1]], [w-point[0], point[1]], erasing)
|
||||
}
|
||||
if (mirror_y) {
|
||||
line(lex, [last_point[0], h-last_point[1]], [point[0], h-point[1]], erasing)
|
||||
}
|
||||
if (mirror_x && mirror_y) {
|
||||
line(lex, [w-last_point[0], h-last_point[1]], [w-point[0], h-point[1]], erasing)
|
||||
}
|
||||
}
|
||||
else {
|
||||
stamp (canvas, brush, point[0], point[1], erasing)
|
||||
if (mirror_x) {
|
||||
stamp (canvas, brush, w-point[0], point[1], erasing)
|
||||
}
|
||||
if (mirror_y) {
|
||||
stamp (canvas, brush, point[0], h-point[1], erasing)
|
||||
}
|
||||
if (mirror_x && mirror_y) {
|
||||
stamp (canvas, brush, w-point[0], h-point[1], erasing)
|
||||
}
|
||||
}
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
}
|
||||
|
||||
function set_last_point (e, point) {
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
}
|
||||
|
||||
function move (e, lex, point) {
|
||||
var w = canvas.w, h = canvas.h
|
||||
line(lex, last_point, point, erasing)
|
||||
if (mirror_x) {
|
||||
line(lex, [w-last_point[0], last_point[1]], [w-point[0], point[1]], erasing)
|
||||
}
|
||||
if (mirror_y) {
|
||||
line(lex, [last_point[0], h-last_point[1]], [point[0], h-point[1]], erasing)
|
||||
}
|
||||
if (mirror_x && mirror_y) {
|
||||
line(lex, [w-last_point[0], h-last_point[1]], [w-point[0], h-point[1]], erasing)
|
||||
}
|
||||
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
}
|
||||
|
||||
function move_toroidal (e, lex, point) {
|
||||
var w = canvas.w, h = canvas.h
|
||||
var src_x_quantile = quantile( last_point[0], w )
|
||||
var src_y_quantile = quantile( last_point[1], h )
|
||||
var dst_x_quantile = quantile( point[0], w )
|
||||
var dst_y_quantile = quantile( point[1], h )
|
||||
var src_x_mod = mod( last_point[0], w )
|
||||
var src_y_mod = mod( last_point[1], h )
|
||||
var dst_x_mod = mod( point[0], w )
|
||||
var dst_y_mod = mod( point[1], h )
|
||||
// if we've moved across the edge of the board, draw two lines
|
||||
if (src_x_quantile != dst_x_quantile || src_y_quantile != dst_y_quantile) {
|
||||
var xa, ya
|
||||
if (src_x_quantile < dst_x_quantile) {
|
||||
xa = [
|
||||
[src_x_mod, dst_x_mod + w],
|
||||
[src_x_mod-w, dst_x_mod],
|
||||
]
|
||||
}
|
||||
else if (src_x_quantile == dst_x_quantile) {
|
||||
xa = [
|
||||
[src_x_mod, dst_x_mod],
|
||||
[src_x_mod, dst_x_mod],
|
||||
]
|
||||
}
|
||||
else {
|
||||
xa = [
|
||||
[src_x_mod, dst_x_mod-w],
|
||||
[src_x_mod+w, dst_x_mod],
|
||||
]
|
||||
}
|
||||
|
||||
if (src_y_quantile < dst_y_quantile) {
|
||||
ya = [
|
||||
[src_y_mod, dst_y_mod + h],
|
||||
[src_y_mod-h, dst_y_mod],
|
||||
]
|
||||
}
|
||||
else if (src_y_quantile == dst_y_quantile) {
|
||||
ya = [
|
||||
[src_y_mod, dst_y_mod],
|
||||
[src_y_mod, dst_y_mod],
|
||||
]
|
||||
}
|
||||
else {
|
||||
ya = [
|
||||
[src_y_mod, dst_y_mod-h],
|
||||
[src_y_mod+h, dst_y_mod],
|
||||
]
|
||||
}
|
||||
line(lex, [ xa[0][0], ya[0][0] ], [ xa[0][1], ya[0][1] ], erasing)
|
||||
line(lex, [ xa[1][0], ya[1][0] ], [ xa[1][1], ya[1][1] ], erasing)
|
||||
}
|
||||
else {
|
||||
var x_a = mod( last_point[0], w )
|
||||
var y_a = mod( last_point[1], h )
|
||||
var x_b = mod( point[0], w )
|
||||
var y_b = mod( point[1], h )
|
||||
var last_point_mod = [x_b, y_b], point_mod = [x_a, y_a]
|
||||
line(lex, last_point_mod, point_mod, erasing)
|
||||
// if (mirror_x) {
|
||||
// line(lex, [w-last_point_mod[0], last_point_mod[1]], [w-point_mod[0], point_mod[1]], erasing)
|
||||
// }
|
||||
// if (mirror_y) {
|
||||
// line(lex, [last_point_mod[0], h-last_point_mod[1]], [point_mod[0], h-point_mod[1]], erasing)
|
||||
// }
|
||||
}
|
||||
last_point[0] = point[0]
|
||||
last_point[1] = point[1]
|
||||
// y = point.y
|
||||
}
|
||||
|
||||
function point (lex, x, y, erasing) {
|
||||
stamp (canvas, brush, x, y, erasing)
|
||||
}
|
||||
|
||||
function line (lex, a, b, erasing) {
|
||||
var len = dist(a[0], a[1], b[0], b[1])
|
||||
var bw = 1
|
||||
var x, y, i;
|
||||
for (var i = 0; i <= len; i += bw) {
|
||||
x = lerp(i / len, a[0], b[0])
|
||||
y = lerp(i / len, a[1], b[1])
|
||||
stamp (canvas, brush, x, y, erasing)
|
||||
}
|
||||
}
|
||||
|
||||
function stamp (canvas, brush, x, y, erasing) {
|
||||
var hh = brush.w/2|0
|
||||
brush.forEach(function(lex, s, t){
|
||||
s = round( s + x-hh )
|
||||
t = round( t + y-hh )
|
||||
if (s >= 0 && s < canvas.w && t >= 0 && t < canvas.h) {
|
||||
if (lex.opacity === 0 && lex.char === ' ') return;
|
||||
var aa = canvas.aa[t][s]
|
||||
undo.save_lex(s, t, aa)
|
||||
if (erasing) {
|
||||
aa.erase(lex)
|
||||
}
|
||||
else {
|
||||
aa.stamp(lex, brush)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function fill (lex, x, y) {
|
||||
var q = [ [x,y] ]
|
||||
var aa = canvas.aa
|
||||
var target = aa[y][x].clone()
|
||||
var n, w = 0, e = 0, j = 0
|
||||
var kk = 0
|
||||
// gets into a weird infinite loop if we don't break here.. :\
|
||||
if (target.eq(lex)) { return }
|
||||
LOOP: while (q.length) {
|
||||
n = q.shift()
|
||||
if (aa[n[1]][n[0]].ne(target)) {
|
||||
continue LOOP
|
||||
}
|
||||
w = e = n[0]
|
||||
j = n[1]
|
||||
WEST: while (w > 0) {
|
||||
if (aa[j][w-1].eq(target)) {
|
||||
w = w-1
|
||||
}
|
||||
else {
|
||||
break WEST
|
||||
}
|
||||
}
|
||||
EAST: while (e < canvas.w-1) {
|
||||
if (aa[j][e+1].eq(target)) {
|
||||
e = e+1
|
||||
}
|
||||
else {
|
||||
break EAST
|
||||
}
|
||||
}
|
||||
for (var i = w; i <= e; i++) {
|
||||
undo.save_lex(i, j, aa[j][i])
|
||||
aa[j][i].assign(lex)
|
||||
if (j > 0 && aa[j-1][i].eq(target)) {
|
||||
q.push([ i, j-1 ])
|
||||
}
|
||||
if (j < canvas.h-1 && aa[j+1][i].eq(target)) {
|
||||
q.push([ i, j+1 ])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var draw = {}
|
||||
draw.down = down
|
||||
draw.set_last_point = set_last_point
|
||||
draw.move = move
|
||||
draw.move_toroidal = move_toroidal
|
||||
draw.stamp = stamp
|
||||
draw.line = line
|
||||
draw.point = point
|
||||
draw.fill = fill
|
||||
return draw
|
||||
|
||||
})()
|
||||
|
||||
var shader = (function(){
|
||||
var fn_str, fn, lex
|
||||
var exports = {}
|
||||
var animating = false
|
||||
|
||||
exports.init = function(){
|
||||
lex = new Lex (0, 0)
|
||||
exports.build(demo_shader.innerHTML)
|
||||
}
|
||||
exports.build = function (fn_str){
|
||||
try {
|
||||
new_fn = new Function('lex', 'x', 'y', 'w', 'h', 't', fn_str)
|
||||
new_fn(lex, 0, 0, 1, 1, 0)
|
||||
}
|
||||
catch (e) {
|
||||
throw 'Shader execution error'
|
||||
}
|
||||
exports.fn = fn = new_fn
|
||||
return fn
|
||||
}
|
||||
exports.run = function(canvas){
|
||||
var t = +new Date
|
||||
shader.canvas = shader.canvas || canvas
|
||||
var w = shader.canvas.w, h = shader.canvas.h
|
||||
shader.canvas.forEach(function(lex, x, y){
|
||||
fn(lex, x, y, w, h, t)
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
exports.toggle = function(state){
|
||||
animating = typeof state == "boolean" ? state : ! animating
|
||||
shader_fps_el.classList.toggle('hidden')
|
||||
return animating
|
||||
}
|
||||
exports.pause = function(){
|
||||
animating = false
|
||||
shader_fps_el.classList.add('hidden')
|
||||
shader.fps_time = 0
|
||||
}
|
||||
exports.play = function(){
|
||||
animating = true
|
||||
shader_fps_el.classList.remove('hidden')
|
||||
}
|
||||
exports.animate = function (t){
|
||||
requestAnimationFrame(exports.animate)
|
||||
if (! animating) { return }
|
||||
if (shader.fps_time){
|
||||
var ms = Date.now() - shader.fps_time
|
||||
fps = 1000 / ms
|
||||
shader_fps_el.innerHTML = (fps | 0) + ' fps'
|
||||
}
|
||||
shader.fps_time = Date.now()
|
||||
exports.run(canvas)
|
||||
}
|
||||
|
||||
return exports
|
||||
|
||||
})()
|
||||
|
@ -0,0 +1,142 @@
|
||||
function Lex (x,y) {
|
||||
if (typeof x == "number") {
|
||||
this.y = y
|
||||
this.x = x
|
||||
this.span = document.createElement("span")
|
||||
}
|
||||
else {
|
||||
this.span = x
|
||||
}
|
||||
this.fg = colors.white
|
||||
this.bg = colors.black
|
||||
this.char = " "
|
||||
this.opacity = 1
|
||||
this.focused = false
|
||||
}
|
||||
Lex.prototype.build = function(){
|
||||
if (isNaN(this.bg) || this.bg == Infinity || this.bg == -Infinity) this.bg = colors.black
|
||||
if (isNaN(this.fg) || this.fg == Infinity || this.fg == -Infinity) this.fg = colors.black
|
||||
this.span.className = this.css()
|
||||
this.span.innerHTML = this.html()
|
||||
}
|
||||
Lex.prototype.css = function(){
|
||||
return (
|
||||
this.focused ?
|
||||
"focused " : ""
|
||||
) + (
|
||||
this.opacity === 0 ?
|
||||
"transparent f" + color_alphabet[modi(this.fg,16)] :
|
||||
"f" + color_alphabet[modi(this.fg,16)] + " b" + color_alphabet[modi(this.bg,16)]
|
||||
)
|
||||
}
|
||||
Lex.prototype.html = function(){
|
||||
return this.char == " " ? " " : this.char || " "
|
||||
}
|
||||
Lex.prototype.read = function(){
|
||||
this.char = this.span.innerHTML
|
||||
return this.char
|
||||
}
|
||||
Lex.prototype.ascii = function(){
|
||||
return this.char || " "
|
||||
}
|
||||
Lex.prototype.sanitize = function(){
|
||||
switch (this.char) {
|
||||
// case "%": return "%"
|
||||
case undefined:
|
||||
case "": return " "
|
||||
default: return this.char
|
||||
}
|
||||
}
|
||||
Lex.prototype.mirc = function(bg_, fg_){
|
||||
var char = this.char || " "
|
||||
var charIsNaN = isNaN(parseInt(char))
|
||||
if ((bg_ == this.fg) && (fg_ == this.bg)) {
|
||||
bg_ = this.bg; fg_ = this.fg
|
||||
return [bg_, fg_, "\x16" + char]
|
||||
} else if ((bg_ != this.bg) && (fg_ == this.fg)) {
|
||||
bg_ = this.bg
|
||||
return [bg_, fg_, "\x03," + ((this.bg&15) < 10 && !charIsNaN ? "0" : "") + (this.bg&15) + char]
|
||||
} else if ((bg_ == this.bg) && (fg_ != this.fg)) {
|
||||
fg_ = this.fg
|
||||
return [bg_, fg_, "\x03" + ((this.fg&15) < 10 && !charIsNaN ? "0" : "") + (this.fg&15) + char]
|
||||
} else {
|
||||
bg_ = this.bg; fg_ = this.fg
|
||||
return [bg_, fg_, "\x03" + (this.fg&15) + "," + ((this.bg&15) < 10 && !charIsNaN ? "0" : "") + (this.bg&15) + char]
|
||||
}
|
||||
}
|
||||
Lex.prototype.ansi = function(){
|
||||
var fg = ansi_fg[ this.fg&15 ]
|
||||
var bg = ansi_bg[ this.bg&15 ]
|
||||
var c = this.sanitize()
|
||||
if (c == "\\") c = "\\\\"
|
||||
if (c == '"') c = '\\"'
|
||||
return "\\e[" + fg + ";" + bg + "m" + c
|
||||
}
|
||||
Lex.prototype.assign = function (lex){
|
||||
this.fg = lex.fg
|
||||
this.bg = lex.bg
|
||||
this.char = lex.char
|
||||
this.opacity = lex.opacity
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.stamp = function (lex, brush){
|
||||
if (brush.draw_fg) this.fg = lex.fg
|
||||
if (brush.draw_bg && lex.opacity > 0) this.bg = lex.bg
|
||||
if (brush.draw_char) this.char = lex.char
|
||||
this.opacity = 1
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.clone = function () {
|
||||
var lex = new Lex (0,0)
|
||||
lex.assign(this)
|
||||
return lex
|
||||
}
|
||||
Lex.prototype.erase = function (){
|
||||
this.fg = fillColor
|
||||
this.bg = fillColor
|
||||
this.char = " "
|
||||
this.opacity = 1
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.eq = function(lex){
|
||||
return lex && this.fg == lex.fg && this.bg == lex.bg && this.char == lex.char
|
||||
}
|
||||
Lex.prototype.eqColor = function(lex){
|
||||
return lex && this.fg == lex.fg && this.bg == lex.bg
|
||||
}
|
||||
Lex.prototype.ne = function(lex){
|
||||
return ! this.eq(lex)
|
||||
}
|
||||
Lex.prototype.clear = function(){
|
||||
this.bg = colors.black
|
||||
this.fg = 0
|
||||
this.char = " "
|
||||
this.opacity = 0
|
||||
this.build()
|
||||
}
|
||||
Lex.prototype.isClear = function(){
|
||||
return this.bg == 1 && this.fg == 0 && this.char == " "
|
||||
}
|
||||
Lex.prototype.focus = function(){
|
||||
if (focused) focused.blur()
|
||||
this.span.classList.add('focused')
|
||||
this.focused = true
|
||||
focused = this
|
||||
}
|
||||
Lex.prototype.blur = function(){
|
||||
focused = null
|
||||
this.span && this.span.classList.remove('focused')
|
||||
this.focused = false
|
||||
this.onBlur && this.onBlur()
|
||||
}
|
||||
Lex.prototype.demolish = function(){
|
||||
if (this.span.parentNode) { this.span.parentNode.removeChild(this.span) }
|
||||
this.span = null
|
||||
}
|
||||
Lex.prototype.key = function(char, keyCode) {
|
||||
if (! char) { return }
|
||||
this.char = char
|
||||
this.fg = brush.fg
|
||||
this.build()
|
||||
return true
|
||||
}
|
@ -0,0 +1,549 @@
|
||||
function Matrix (w,h,f){
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.w = w
|
||||
this.h = h
|
||||
this.f = f
|
||||
this.focus_x = 0
|
||||
this.focus_y = 0
|
||||
this.initialize()
|
||||
}
|
||||
Matrix.prototype.initialize = function(f){
|
||||
var w = this.w || 1, h = this.h || 1, f = f || this.f
|
||||
var aa = new Array (h)
|
||||
for (var y = 0; y < h; y++) {
|
||||
aa[y] = new Array (w)
|
||||
for (var x = 0; x < w; x++) {
|
||||
aa[y][x] = f(x,y)
|
||||
}
|
||||
}
|
||||
this.aa = aa
|
||||
}
|
||||
Matrix.prototype.rebuild = function (){
|
||||
this.demolish()
|
||||
this.initialize()
|
||||
this.append()
|
||||
this.bind()
|
||||
this.generate && this.generate()
|
||||
this.focus_clamp()
|
||||
check_if_lost_focus()
|
||||
}
|
||||
Matrix.prototype.clone = function () {
|
||||
var base = this
|
||||
var clone = new Matrix(this.w, this.h, function(x,y){
|
||||
return base.getCell(x,y).clone()
|
||||
})
|
||||
clone.f = this.f
|
||||
return clone
|
||||
}
|
||||
Matrix.prototype.assign = function (mat) {
|
||||
var base = this
|
||||
this.demolish()
|
||||
this.w = mat.w
|
||||
this.h = mat.h
|
||||
// this.f = function(){}
|
||||
this.initialize(function(x,y){
|
||||
var el = mat.getCell(x,y).clone()
|
||||
el.build()
|
||||
return el
|
||||
})
|
||||
this.append()
|
||||
this.bind()
|
||||
check_if_lost_focus()
|
||||
return this
|
||||
}
|
||||
|
||||
Matrix.prototype.bind = function () {}
|
||||
Matrix.prototype.demolish = function (){
|
||||
this.forEach(function(lex){
|
||||
lex.demolish()
|
||||
})
|
||||
while (this.rapper && this.rapper.firstChild) {
|
||||
this.rapper.removeChild(this.rapper.firstChild);
|
||||
}
|
||||
this.aa.forEach(function(row){
|
||||
row.length = 0
|
||||
})
|
||||
this.aa.length = 0
|
||||
}
|
||||
Matrix.prototype.forEach = function(f){
|
||||
this.aa.forEach(function(row, y){
|
||||
row.forEach(function(lex, x){
|
||||
f(lex, x, y)
|
||||
})
|
||||
})
|
||||
}
|
||||
Matrix.prototype.focus_clamp = function(){
|
||||
this.focus_x = clamp(this.focus_x, 0, this.w - 1)
|
||||
this.focus_y = clamp(this.focus_y, 0, this.h - 1)
|
||||
}
|
||||
Matrix.prototype.focus_add = function(x, y){
|
||||
this.focus(this.focus_x + x, this.focus_y + y)
|
||||
}
|
||||
Matrix.prototype.focus = function(x, y){
|
||||
if (x === undefined) x = this.focus_x
|
||||
if (y === undefined) y = this.focus_y
|
||||
x = mod(x, this.w)
|
||||
y = mod(y, this.h)
|
||||
this.focus_x = x
|
||||
this.focus_y = y
|
||||
|
||||
//focused_input = this
|
||||
this.aa[y][x].focus()
|
||||
}
|
||||
Matrix.prototype.focusLex = function(y,x){
|
||||
if (x < 0) {
|
||||
y -= 1
|
||||
}
|
||||
if (x > this.aa[0].length) {
|
||||
y += 1
|
||||
}
|
||||
this.aa[mod(y,this.h)][mod(x,this.w)].focus()
|
||||
}
|
||||
Matrix.prototype.clear = function(){
|
||||
this.forEach(function(lex,x,y){ lex.clear() })
|
||||
}
|
||||
Matrix.prototype.erase = function(){
|
||||
this.forEach(function(lex,x,y){ lex.erase() })
|
||||
}
|
||||
Matrix.prototype.fill = function(lex){
|
||||
this.fg = lex.fg
|
||||
this.bg = lex.bg
|
||||
this.char = lex.char
|
||||
this.opacity = lex.opacity
|
||||
this.forEach(function(el,x,y){
|
||||
el.assign(lex)
|
||||
el.build()
|
||||
})
|
||||
}
|
||||
|
||||
Matrix.prototype.build = function(){
|
||||
this.forEach(function(lex,x,y){
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
Matrix.prototype.append = function(rapper){
|
||||
rapper = this.rapper = rapper || this.rapper
|
||||
if (! this.rapper) return
|
||||
this.aa.forEach(function(row, y){
|
||||
var div = document.createElement("div")
|
||||
row.forEach(function(lex, x) {
|
||||
div.appendChild(lex.span)
|
||||
})
|
||||
rapper.appendChild( div )
|
||||
})
|
||||
}
|
||||
Matrix.prototype.region = function(w,h,x,y) {
|
||||
w = w || 1
|
||||
h = h || 1
|
||||
x = x || 0
|
||||
y = y || 0
|
||||
var parent = this
|
||||
var mat = new Matrix(w, h, function(x,y){
|
||||
return parent.aa[y][x]
|
||||
})
|
||||
mat.f = this.f
|
||||
return mat
|
||||
}
|
||||
Matrix.prototype.setCell = function(lex,x,y){
|
||||
this.aa[y] && this.aa[y][x] && this.aa[y][x].assign(lex)
|
||||
}
|
||||
Matrix.prototype.getCell = function(x,y){
|
||||
if (this.aa[y] && this.aa[y][x]) return this.aa[y][x]
|
||||
else return null
|
||||
}
|
||||
Matrix.prototype.get = function(x,y){
|
||||
y = floor(mod(y || 0, this.h))
|
||||
x = floor(mod(x || 0, this.w))
|
||||
if (this.aa[y] && this.aa[y][x]) return this.aa[y][x]
|
||||
else return null
|
||||
}
|
||||
|
||||
Matrix.prototype.resize = function(w,h){
|
||||
w = w || canvas.w
|
||||
h = h || canvas.h
|
||||
var div, row, lex
|
||||
var f = this.f, old_h = this.aa.length, old_w = this.aa[0].length
|
||||
var rapper = this.rapper
|
||||
w = max(w, 1)
|
||||
h = max(h, 1)
|
||||
if (h < old_h) {
|
||||
for (var y = old_h; y > h; y--) {
|
||||
row = this.aa.pop()
|
||||
div = row[0].span.parentNode
|
||||
row.forEach(function(lex, x){
|
||||
lex.demolish()
|
||||
})
|
||||
div.parentNode.removeChild(div)
|
||||
}
|
||||
}
|
||||
else if (h > old_h) {
|
||||
for (var y = old_h; y < h; y++) {
|
||||
div = document.createElement("div")
|
||||
rapper.appendChild( div )
|
||||
this.aa[y] = new Array (w)
|
||||
for (var x = 0; x < w; x++) {
|
||||
lex = this.aa[y][x] = f(x,y)
|
||||
div.appendChild(lex.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w < old_w) {
|
||||
this.aa.forEach(function(row, y){
|
||||
while (row.length > w) {
|
||||
lex = row.pop()
|
||||
lex.demolish()
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (w > old_w) {
|
||||
this.aa.forEach(function(row, y){
|
||||
div = row[0].span.parentNode
|
||||
for (var x = row.length; x < w; x++) {
|
||||
lex = row[x] = f(x,y)
|
||||
div.appendChild(lex.span)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.w = w
|
||||
this.h = h
|
||||
this.bind && this.bind()
|
||||
this.focus_clamp()
|
||||
if (this.rapper && this.rapper.parentNode != document.body) {
|
||||
this.resize_rapper()
|
||||
}
|
||||
}
|
||||
Matrix.prototype.resize_rapper = function(){
|
||||
var cell = canvas.aa[0][0].span
|
||||
var cw = cell.offsetWidth
|
||||
var ch = cell.offsetHeight
|
||||
// if (canvas.grid) { ch++ }
|
||||
var width = cw * this.aa[0].length
|
||||
var height = ch * this.aa.length
|
||||
if (canvas.grid) { width++; height++ }
|
||||
if (this.rotated) {
|
||||
this.rapper.parentNode.classList.add("rotated")
|
||||
this.rapper.parentNode.style.height = (width) + "px"
|
||||
this.rapper.parentNode.style.width = (height) + "px"
|
||||
this.rapper.style.top = (width/2) + "px"
|
||||
// this.rapper.style.left = ((canvas_rapper.offsetHeight+20)/2) + "px"
|
||||
}
|
||||
else {
|
||||
this.rapper.parentNode.classList.remove("rotated")
|
||||
this.rapper.parentNode.style.height = ""
|
||||
this.rapper.style.width =
|
||||
this.rapper.parentNode.style.width = (width) + "px"
|
||||
this.rapper.style.top = ""
|
||||
// canvas_rapper.style.left = "auto"
|
||||
}
|
||||
}
|
||||
Matrix.prototype.ascii = function () {
|
||||
var lines = this.aa.map(function(row, y){
|
||||
var last, line = ""
|
||||
row.forEach(function(lex, x) {
|
||||
line += lex.ascii()
|
||||
})
|
||||
return line // .replace(/\s+$/,"")
|
||||
})
|
||||
var txt = lines.join("\n")
|
||||
return txt
|
||||
}
|
||||
Matrix.prototype.ansi = function (opts) {
|
||||
var lines = this.aa.map(function(row, y){
|
||||
var last, line = ""
|
||||
row.forEach(function(lex, x) {
|
||||
if (lex.eqColor(last)) {
|
||||
line += lex.sanitize()
|
||||
}
|
||||
else {
|
||||
line += lex.ansi()
|
||||
last = lex
|
||||
}
|
||||
})
|
||||
return line
|
||||
})
|
||||
var txt = lines.filter(function(line){ return line.length > 0 }).join('\\e[0m\\n') + "\\e[0m"
|
||||
return 'echo -e "' + txt + '"'
|
||||
}
|
||||
Matrix.prototype.mirc = function (opts) {
|
||||
var cutoff = false
|
||||
var lines = this.aa.map(function(row, y){
|
||||
var last, line = ""
|
||||
row.forEach(function(lex, x) {
|
||||
var bg_ = -1, fg_ = 15
|
||||
if (lex.eqColor(last)) {
|
||||
line += lex.sanitize()
|
||||
}
|
||||
else {
|
||||
[bg_, fg_, line_] = lex.mirc(bg_, fg_)
|
||||
line += line_; last = lex;
|
||||
}
|
||||
})
|
||||
if (opts && opts.cutoff && line.length > opts.cutoff) {
|
||||
cutoff = true
|
||||
}
|
||||
return line
|
||||
})
|
||||
|
||||
var txt = lines.filter(function(line){ return line.length > 0 }).join('\n')
|
||||
|
||||
if (cutoff) {
|
||||
txt = new String(txt)
|
||||
txt.cutoff = true
|
||||
}
|
||||
return txt
|
||||
}
|
||||
Matrix.prototype.irssi = function(opts){
|
||||
var mirc = this.mirc(opts)
|
||||
var txt = mirc
|
||||
// .replace(/\%/g, '%%')
|
||||
.replace(/\\/g, '\\x5C')
|
||||
.replace(/\"/g, '\\\"')
|
||||
// .replace(/\'/g, '\\\'')
|
||||
.replace(/\`/g, '\\\`')
|
||||
.replace(/\$/g, '\\$')
|
||||
// .replace(/\n\s+/g, '\n')
|
||||
// .replace(/\s+$/g, '\n')
|
||||
// .replace(/^\n+/, '')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\x02/g, '\\x02')
|
||||
.replace(/\x03/g, '\\x03')
|
||||
|
||||
txt = unicode.escapeToEscapedBytes(txt)
|
||||
txt = '/exec -out printf "%b" "' + txt + '"\n'
|
||||
if (mirc.cutoff){
|
||||
txt = new String(txt)
|
||||
txt.cutoff = true
|
||||
}
|
||||
return txt
|
||||
}
|
||||
|
||||
var undo = (function(){
|
||||
|
||||
var max_states = 200;
|
||||
|
||||
// undotimetotal = 0;
|
||||
|
||||
var stack = {undo: [], redo: []};
|
||||
var current_undo = null;
|
||||
var dom = {undo: undo_el, redo: redo_el};
|
||||
dom.undo.is_visible = dom.redo.is_visible = false
|
||||
|
||||
var LexState = function(lex){
|
||||
this.fg = lex.fg;
|
||||
this.bg = lex.bg;
|
||||
this.char = lex.char;
|
||||
this.opacity = lex.opacity;
|
||||
};
|
||||
|
||||
var update_dom_visibility = function(type){
|
||||
var el = dom[type]
|
||||
if (el.is_visible){
|
||||
if (stack[type].length === 0) {
|
||||
el.classList.add('hidden')
|
||||
el.is_visible = false
|
||||
}
|
||||
} else if (stack[type].length > 0){
|
||||
el.classList.remove('hidden')
|
||||
el.is_visible = true
|
||||
}
|
||||
}
|
||||
var update_dom = function(){
|
||||
update_dom_visibility('undo')
|
||||
update_dom_visibility('redo')
|
||||
}
|
||||
|
||||
// state is an undo or redo state that might contain these props
|
||||
// { lexs: {'0,0': LexState, ...}, // for sparse lex changes (eg brush, fill)
|
||||
// focus: {x:, y: },
|
||||
// size: {w:, h: },
|
||||
// rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...]
|
||||
// }
|
||||
var new_state = function(){
|
||||
var state = {lexs:{}};
|
||||
save_focus(canvas.focus_x, canvas.focus_y, state)
|
||||
return state
|
||||
}
|
||||
var new_redo = function(){
|
||||
return new_state()
|
||||
}
|
||||
var new_undo = function(){
|
||||
current_undo = new_state()
|
||||
stack.redo = []
|
||||
stack.undo.push(current_undo)
|
||||
if (stack.undo.length > max_states) stack.undo.shift();
|
||||
update_dom()
|
||||
return current_undo
|
||||
}
|
||||
|
||||
var save_focus = function(x, y, state){
|
||||
state = state || current_undo
|
||||
state.focus = {x:x, y:y}
|
||||
}
|
||||
var save_size = function(w, h, state){
|
||||
state = state || current_undo
|
||||
state.size = {w:w, h:h};
|
||||
}
|
||||
// the reason for stringifying the x y coords is so that each
|
||||
// coordinate is saved only once in an undo state.
|
||||
// otherwise there would be problems with, eg, a brush stroke
|
||||
// that passed over the same grid cell twice.
|
||||
var save_lex = function(x, y, lex, state){
|
||||
// var start = Date.now()
|
||||
state = state || current_undo
|
||||
var lexs = state.lexs;
|
||||
var xy = x + "," + y;
|
||||
if (xy in lexs) return;
|
||||
lexs[xy] = new LexState(lex)
|
||||
// undotimetotal += Date.now() - start
|
||||
}
|
||||
var save_focused_lex = function(state){
|
||||
state = state || current_undo
|
||||
var x = canvas.focus_x
|
||||
var y = canvas.focus_y
|
||||
save_lex(x, y, canvas.aa[y][x], state)
|
||||
}
|
||||
var save_rect = function(xpos, ypos, w, h, state){
|
||||
if (w === 0 || h === 0) return;
|
||||
state = state || current_undo;
|
||||
state.rects = state.rects || []
|
||||
var aa = canvas.aa;
|
||||
var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []}
|
||||
var lexs = rect.lexs
|
||||
var xlen = xpos + w
|
||||
var ylen = ypos + h
|
||||
for (var y = ypos; y < ylen; y++){
|
||||
var aay = aa[y]
|
||||
for (var x = xpos; x < xlen; x++){
|
||||
lexs.push(new LexState(aay[x]))
|
||||
}
|
||||
}
|
||||
state.rects.push(rect)
|
||||
}
|
||||
var save_resize = function(w, h, old_w, old_h, state){
|
||||
state = state || current_undo
|
||||
save_size(old_w, old_h, state)
|
||||
if (old_w > w){
|
||||
// .---XX
|
||||
// | XX
|
||||
// |___XX
|
||||
save_rect(w, 0, old_w - w, old_h, state)
|
||||
if (old_h > h){
|
||||
// .----.
|
||||
// | |
|
||||
// XXXX_|
|
||||
save_rect(0, h, w, old_h - h, state)
|
||||
}
|
||||
} else if (old_h > h){
|
||||
// .----.
|
||||
// | |
|
||||
// XXXXXX
|
||||
save_rect(0, h, old_w, old_h - h, state)
|
||||
}
|
||||
}
|
||||
|
||||
var restore_state = function(state){
|
||||
// all redo states will have a cached undo state on them
|
||||
// an undo state might have a cached redo state
|
||||
// if it doesn't have one, generate one
|
||||
var make_redo = ! ('redo' in state || 'undo' in state);
|
||||
var aa = canvas.aa
|
||||
var lex, lexs;
|
||||
|
||||
if (make_redo){
|
||||
state.redo = new_redo()
|
||||
|
||||
// copy saved rects that intersect with current canvas size
|
||||
// important to do this before resizing canvas
|
||||
if ('rects' in state){
|
||||
for (var ri=0, rect; rect=state.rects[ri]; ri++){
|
||||
if (rect.x >= canvas.w ||
|
||||
rect.y >= canvas.h) continue;
|
||||
var w = Math.min(rect.w, canvas.w - rect.x)
|
||||
var h = Math.min(rect.h, canvas.h - rect.y)
|
||||
save_rect(rect.x, rect.y, w, h, state.redo)
|
||||
}
|
||||
}
|
||||
if ('size' in state){
|
||||
save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo)
|
||||
}
|
||||
}
|
||||
|
||||
if ('size' in state){
|
||||
canvas.resize(state.size.w, state.size.h, true);
|
||||
}
|
||||
|
||||
if ('rects' in state){
|
||||
for (var ri=0, rect; rect=state.rects[ri]; ri++){
|
||||
lexs = rect.lexs
|
||||
for (var li=0; lex=lexs[li]; li++){
|
||||
var x = (li % rect.w) + rect.x
|
||||
var y = ((li / rect.w)|0) + rect.y
|
||||
aa[y][x].assign(lex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lexs = state.lexs
|
||||
for (var key in lexs){
|
||||
var xy = key.split(',');
|
||||
lex = aa[xy[1]][xy[0]]
|
||||
if (make_redo)
|
||||
save_lex(xy[0], xy[1], lex, state.redo)
|
||||
lex.assign(lexs[key])
|
||||
}
|
||||
|
||||
if ('focus' in state){
|
||||
canvas.focus_x = state.focus.x
|
||||
canvas.focus_y = state.focus.y
|
||||
if (current_canvas === canvas){
|
||||
canvas.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var undo = function(){
|
||||
var state = stack.undo.pop();
|
||||
if (!state) return;
|
||||
|
||||
restore_state(state)
|
||||
|
||||
// now take the applied undo state and store it on the redo state
|
||||
// and push the redo state to the redo stack
|
||||
state.redo.undo = state
|
||||
stack.redo.push(state.redo)
|
||||
delete state.redo
|
||||
|
||||
update_dom()
|
||||
}
|
||||
|
||||
var redo = function(){
|
||||
var state = stack.redo.pop();
|
||||
if (!state) return;
|
||||
|
||||
restore_state(state)
|
||||
|
||||
state.undo.redo = state
|
||||
stack.undo.push(state.undo)
|
||||
delete state.undo
|
||||
|
||||
update_dom()
|
||||
}
|
||||
|
||||
return {
|
||||
stack: stack,
|
||||
new: new_undo,
|
||||
// new_redo: new_redo,
|
||||
save_focus: save_focus,
|
||||
save_size: save_size,
|
||||
save_lex: save_lex,
|
||||
save_focused_lex: save_focused_lex,
|
||||
save_rect: save_rect,
|
||||
save_resize: save_resize,
|
||||
undo: undo,
|
||||
redo: redo
|
||||
}
|
||||
|
||||
})()
|
@ -0,0 +1,108 @@
|
||||
var brush = (function(){
|
||||
|
||||
var brush = new Matrix (5, 5, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
lex.build()
|
||||
return lex
|
||||
})
|
||||
|
||||
brush.modified = false
|
||||
|
||||
brush.mask = blit.circle
|
||||
|
||||
brush.generate = function(){
|
||||
brush.fill(brush)
|
||||
brush.mask(brush)
|
||||
}
|
||||
|
||||
brush.bind = function(){
|
||||
|
||||
var last_point = [0,0]
|
||||
var dragging = false
|
||||
var erasing = false
|
||||
|
||||
brush.forEach(function(lex, x, y){
|
||||
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
|
||||
var point = [x,y]
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
})
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
current_canvas = brush
|
||||
brush.modified = true
|
||||
dragging = true
|
||||
erasing = (e.which == "3" || e.ctrlKey)
|
||||
if (erasing) {
|
||||
lex.clear()
|
||||
}
|
||||
else {
|
||||
fillColor = brush.bg
|
||||
lex.assign(brush)
|
||||
}
|
||||
brush.focus(x, y)
|
||||
})
|
||||
lex.span.addEventListener('mousemove', function(e){
|
||||
e.preventDefault()
|
||||
if (! dragging) {
|
||||
return
|
||||
}
|
||||
erasing = (e.which == "3" || e.ctrlKey)
|
||||
if (erasing) {
|
||||
lex.clear()
|
||||
}
|
||||
else {
|
||||
lex.assign(brush)
|
||||
}
|
||||
brush.focus(x, y)
|
||||
})
|
||||
})
|
||||
window.addEventListener('mouseup', function(e){
|
||||
dragging = erasing = false
|
||||
})
|
||||
}
|
||||
|
||||
brush.resize = function(w, h){
|
||||
w = this.w = clamp(w, this.min, this.max)
|
||||
h = this.h = clamp(h, this.min, this.max)
|
||||
brush.rebuild()
|
||||
controls.brush_w.char = "" + w
|
||||
controls.brush_w.build()
|
||||
controls.brush_h.char = "" + h
|
||||
controls.brush_h.build()
|
||||
}
|
||||
brush.size_add = function(w, h){
|
||||
brush.resize(brush.w + w, brush.h + h)
|
||||
}
|
||||
brush.expand = function(i){
|
||||
brush.size_add(i, i)
|
||||
}
|
||||
brush.contract = function(i){
|
||||
brush.size_add(-i, -i)
|
||||
}
|
||||
|
||||
brush.load = function(lex){
|
||||
brush.char = lex.char
|
||||
brush.fg = lex.fg
|
||||
brush.bg = lex.bg
|
||||
brush.opacity = 1
|
||||
}
|
||||
|
||||
brush.min = 1
|
||||
brush.max = 100
|
||||
|
||||
brush.char = " "
|
||||
brush.fg = 0
|
||||
brush.bg = 1
|
||||
brush.opacity = 1
|
||||
|
||||
brush.draw_fg = true
|
||||
brush.draw_bg = true
|
||||
brush.draw_char = true
|
||||
|
||||
return brush
|
||||
|
||||
})()
|
@ -0,0 +1,144 @@
|
||||
var canvas = current_canvas = (function(){
|
||||
|
||||
var cols = 100
|
||||
var rows = 30
|
||||
|
||||
var canvas = new Matrix (cols, rows, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
lex.build()
|
||||
return lex
|
||||
})
|
||||
|
||||
canvas.bind = function(){
|
||||
|
||||
canvas.forEach(function(lex, x, y){
|
||||
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
var point = [x,y]
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
})
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
if (is_mobile) return
|
||||
e.preventDefault()
|
||||
dragging = true
|
||||
current_canvas = canvas
|
||||
if (e.altKey) {
|
||||
if (e.shiftKey) {
|
||||
blit.copy_from(canvas, brush, floor(x-brush.w/2), floor(y-brush.h/2))
|
||||
brush.mask(brush)
|
||||
draw.set_last_point(e, point)
|
||||
}
|
||||
else {
|
||||
brush.load(lex)
|
||||
brush.generate()
|
||||
dragging = false
|
||||
}
|
||||
return
|
||||
}
|
||||
else if (drawing) {
|
||||
undo.new()
|
||||
draw.down(e, lex, point)
|
||||
}
|
||||
else if (selecting) {
|
||||
selection.down(e, lex, point)
|
||||
}
|
||||
else if (transforming) {
|
||||
transform.down(e, lex, point)
|
||||
}
|
||||
else if (filling) {
|
||||
undo.new()
|
||||
draw.fill(brush, x, y)
|
||||
}
|
||||
canvas.focus(x, y)
|
||||
})
|
||||
|
||||
lex.span.addEventListener("mousemove", function(e){
|
||||
mouse.x = x
|
||||
mouse.y = y
|
||||
if (is_mobile) return
|
||||
if (! dragging) return
|
||||
if (drawing) {
|
||||
draw.move(e, lex, point)
|
||||
}
|
||||
else if (selecting) {
|
||||
selection.move(e, lex, point)
|
||||
}
|
||||
else if (transforming) {
|
||||
transform.move(e, lex, point)
|
||||
}
|
||||
canvas.focus(x, y)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
if (is_mobile) {
|
||||
canvas.rapper.addEventListener('touchstart', function(e){
|
||||
e.preventDefault()
|
||||
var x, y, point, lex
|
||||
x = (e.touches[0].pageX - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetWidth
|
||||
y = (e.touches[0].pageY - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetHeight
|
||||
x = ~~clamp(x, 0, canvas.aa[0].length-1)
|
||||
y = ~~clamp(y, 0, canvas.aa.length-1)
|
||||
point = [x,y]
|
||||
lex = canvas.aa[y][x]
|
||||
dragging = true
|
||||
if (drawing) {
|
||||
undo.new()
|
||||
draw.down(e, lex, point)
|
||||
}
|
||||
else if (filling) {
|
||||
undo.new()
|
||||
draw.fill(brush, x, y)
|
||||
}
|
||||
canvas.focus(x, y)
|
||||
})
|
||||
canvas.rapper.addEventListener("touchmove", function(e){
|
||||
e.preventDefault()
|
||||
var x, y, point, lex
|
||||
x = (e.touches[0].pageX - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetWidth
|
||||
y = (e.touches[0].pageY - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetHeight
|
||||
x = ~~clamp(x, 0, canvas.aa[0].length-1)
|
||||
y = ~~clamp(y, 0, canvas.aa.length-1)
|
||||
point = [x,y]
|
||||
lex = canvas.aa[y][x]
|
||||
if (! dragging) return
|
||||
shader_el.innerHTML = point.join(",")
|
||||
if (drawing) {
|
||||
draw.move(e, lex, point)
|
||||
}
|
||||
canvas.focus(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
canvas.min = 1
|
||||
canvas.max = 999
|
||||
|
||||
// canvas.resize(1, 1, true) // wont create undo state
|
||||
canvas.resize = function(w, h, no_undo){
|
||||
var old_w = this.w, old_h = this.h
|
||||
w = this.w = clamp(w, this.min, this.max)
|
||||
h = this.h = clamp(h, this.min, this.max)
|
||||
if (old_w === w && old_h === h) return;
|
||||
|
||||
if (!no_undo){
|
||||
undo.new()
|
||||
undo.save_resize(w, h, old_w, old_h)
|
||||
}
|
||||
|
||||
canvas.__proto__.resize.call(canvas, w, h)
|
||||
controls.canvas_w.char = "" + w
|
||||
controls.canvas_w.build()
|
||||
controls.canvas_h.char = "" + h
|
||||
controls.canvas_h.build()
|
||||
}
|
||||
canvas.size_add = function(w, h){
|
||||
canvas.resize(canvas.w + w, canvas.h + h)
|
||||
}
|
||||
|
||||
return canvas
|
||||
|
||||
})()
|
342
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/assets/js/ui/controls.js
vendored
Normal file
342
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/assets/js/ui/controls.js
vendored
Normal file
@ -0,0 +1,342 @@
|
||||
var controls = (function(){
|
||||
|
||||
var controls = {}
|
||||
|
||||
controls.cross = new Tool (cross_el)
|
||||
controls.cross.use = function(){
|
||||
if (brush.mask == blit.cross) {
|
||||
controls.cross.el.innerHTML = "ssoɹɔ"
|
||||
brush.mask = blit.inverted_cross
|
||||
}
|
||||
else {
|
||||
controls.cross.el.innerHTML = "cross"
|
||||
brush.mask = blit.cross
|
||||
}
|
||||
brush.generate()
|
||||
drawing = true
|
||||
brush.modified = false
|
||||
}
|
||||
controls.cross.done = function(){
|
||||
controls.cross.el.innerHTML = "cross"
|
||||
drawing = false
|
||||
}
|
||||
|
||||
controls.circle = new Tool (circle_el)
|
||||
controls.circle.use = function(){
|
||||
brush.mask = blit.circle
|
||||
brush.generate()
|
||||
drawing = true
|
||||
brush.modified = false
|
||||
}
|
||||
controls.circle.done = function(){
|
||||
drawing = false
|
||||
}
|
||||
|
||||
controls.square = new Tool (square_el)
|
||||
controls.square.use = function(){
|
||||
brush.mask = blit.square
|
||||
brush.generate()
|
||||
brush.modified = false
|
||||
drawing = true
|
||||
}
|
||||
controls.square.done = function(){
|
||||
drawing = false
|
||||
}
|
||||
|
||||
controls.text = new Tool (text_el)
|
||||
controls.text.use = function(){
|
||||
current_filetool && current_filetool.blur()
|
||||
}
|
||||
|
||||
controls.select = new Tool (select_el)
|
||||
controls.select.use = function(){
|
||||
selection.show()
|
||||
}
|
||||
controls.select.done = function(){
|
||||
selection.hide()
|
||||
}
|
||||
|
||||
controls.rotate = new Tool (rotate_el)
|
||||
controls.rotate.use = function(){
|
||||
transform.set_mode('rotate')
|
||||
}
|
||||
controls.rotate.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.scale = new Tool (scale_el)
|
||||
controls.scale.use = function(){
|
||||
transform.set_mode('scale')
|
||||
}
|
||||
controls.scale.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.slice = new Tool (slice_el)
|
||||
controls.slice.use = function(){
|
||||
transform.set_mode('slice')
|
||||
}
|
||||
controls.slice.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.translate = new Tool (translate_el)
|
||||
controls.translate.use = function(){
|
||||
transform.set_mode('translate')
|
||||
}
|
||||
controls.translate.done = function(){
|
||||
transform.done()
|
||||
}
|
||||
|
||||
controls.fill = new Tool (fill_el)
|
||||
controls.fill.use = function(){
|
||||
filling = true
|
||||
document.body.classList.add("bucket")
|
||||
}
|
||||
controls.fill.done = function(){
|
||||
filling = false
|
||||
document.body.classList.remove("bucket")
|
||||
}
|
||||
|
||||
controls.undo = new BlurredTool (undo_el)
|
||||
controls.undo.use = function(){
|
||||
undo.undo()
|
||||
}
|
||||
|
||||
controls.redo = new BlurredTool (redo_el)
|
||||
controls.redo.use = function(){
|
||||
undo.redo()
|
||||
}
|
||||
|
||||
controls.clear = new BlurredTool (clear_el)
|
||||
controls.clear.use = function(){
|
||||
undo.new()
|
||||
undo.save_rect(0, 0, canvas.w, canvas.h)
|
||||
canvas.erase()
|
||||
current_filetool && current_filetool.blur()
|
||||
}
|
||||
|
||||
controls.grid = new BlurredCheckbox (grid_el)
|
||||
controls.grid.memorable = true
|
||||
controls.grid.use = function(state){
|
||||
state = typeof state == "boolean" ? state : ! document.body.classList.contains("grid")
|
||||
document.body.classList[ state ? "add" : "remove" ]('grid')
|
||||
letters.grid = palette.grid = canvas.grid = state
|
||||
canvas.resize_rapper()
|
||||
palette.resize_rapper()
|
||||
letters.resize_rapper()
|
||||
if (! selection.hidden) selection.reposition()
|
||||
this.update( state )
|
||||
}
|
||||
ClipboardTool = FileTool.extend({
|
||||
blur: function(){
|
||||
this.__blur()
|
||||
clipboard.hide()
|
||||
}
|
||||
})
|
||||
controls.save = new ClipboardTool (save_el)
|
||||
controls.save.use = function(){
|
||||
clipboard.show()
|
||||
clipboard.export_mode()
|
||||
}
|
||||
controls.load = new ClipboardTool (load_el)
|
||||
controls.load.use = function(){
|
||||
// console.log("use")
|
||||
clipboard.show()
|
||||
clipboard.import_mode()
|
||||
}
|
||||
|
||||
controls.save_format = new RadioGroup(format_el)
|
||||
controls.save_format.name = 'save_format'
|
||||
controls.save_format.memorable = true
|
||||
var cs = controls.save_format.controls
|
||||
cs.mirc.use = cs.irssi.use = cs.ascii.use = function(){
|
||||
clipboard.export_data()
|
||||
}
|
||||
//
|
||||
|
||||
var ShaderTool = FileTool.extend({
|
||||
active: false,
|
||||
use: function(state){
|
||||
this.active = typeof state == "boolean" ? state : ! this.active
|
||||
if (this.active) {
|
||||
shader_rapper.style.display = "block"
|
||||
shader_textarea.focus()
|
||||
} else {
|
||||
shader_rapper.style.display = "none"
|
||||
}
|
||||
},
|
||||
done: function(){
|
||||
this.use(false)
|
||||
}
|
||||
})
|
||||
controls.shader = new ShaderTool (shader_el)
|
||||
shader_textarea.value = shader_textarea.value || demo_shader.innerHTML
|
||||
shader_textarea.addEventListener("input", function(){
|
||||
var fn = shader.build(shader_textarea.value)
|
||||
fn && shader.run(canvas)
|
||||
})
|
||||
controls.animate = new BlurredCheckbox (animate_checkbox)
|
||||
controls.animate.use = function(state){
|
||||
var state = shader.toggle()
|
||||
this.update(state)
|
||||
// controls.shader.focus()
|
||||
controls.shader.use(true)
|
||||
}
|
||||
|
||||
controls.shader_target = new RadioGroup(shader_target_el)
|
||||
var cs = controls.shader_target.controls
|
||||
cs.canvas.use = function(){ shader.canvas = canvas }
|
||||
cs.brush.use = function(){ shader.canvas = brush }
|
||||
cs.selection.use = function(){ shader.canvas = selection.canvas }
|
||||
|
||||
controls.experimental_palette = new HiddenCheckbox (experimental_palette_toggle)
|
||||
controls.experimental_palette.memorable = true
|
||||
controls.experimental_palette.use = function(state){
|
||||
var state = palette.experimental(state)
|
||||
this.update(state)
|
||||
}
|
||||
|
||||
controls.advanced = new BlurredCheckbox (advanced_checkbox)
|
||||
controls.advanced.memorable = true
|
||||
controls.advanced.use = function(state){
|
||||
console.log(state)
|
||||
state = typeof state == "boolean" ? state : ! document.body.classList.contains('panke')
|
||||
if (state)
|
||||
document.body.classList.add('panke')
|
||||
else
|
||||
document.body.classList.remove('panke')
|
||||
this.update(state)
|
||||
}
|
||||
|
||||
/*
|
||||
controls.nopaint = new HiddenCheckbox (nopaint_toggle)
|
||||
controls.nopaint.memorable = true
|
||||
controls.nopaint.on = "N"
|
||||
controls.nopaint.use = function(state){
|
||||
var state = nopaint.toggle(state)
|
||||
this.update(state)
|
||||
}
|
||||
*/
|
||||
|
||||
//
|
||||
|
||||
controls.fg = new BlurredCheckbox (fg_checkbox)
|
||||
controls.fg.use = function(state){
|
||||
brush.draw_fg = state || ! brush.draw_fg
|
||||
this.update(brush.draw_fg)
|
||||
}
|
||||
|
||||
controls.bg = new BlurredCheckbox (bg_checkbox)
|
||||
controls.bg.use = function(state){
|
||||
brush.draw_bg = state || ! brush.draw_bg
|
||||
this.update(brush.draw_bg)
|
||||
}
|
||||
|
||||
controls.char = new BlurredCheckbox (char_checkbox)
|
||||
controls.char.use = function(state){
|
||||
brush.draw_char = state || ! brush.draw_char
|
||||
this.update(brush.draw_char)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// controls.turn = new BlurredCheckbox (turn_checkbox)
|
||||
// controls.turn.memorable = true
|
||||
// controls.turn.use = function(state){
|
||||
// canvas.rotated = typeof state == "boolean" ? state : ! canvas.rotated
|
||||
// canvas.resize_rapper()
|
||||
// this.update(canvas.rotated)
|
||||
// }
|
||||
|
||||
// controls.pixels = new BlurredCheckbox (pixels_checkbox)
|
||||
// controls.pixels.memorable = true
|
||||
// controls.pixels.use = function(state){
|
||||
// canvas.pixels = typeof state == "boolean" ? state : ! canvas.pixels
|
||||
// document.body.classList.toggle("pixels", canvas.pixels)
|
||||
// this.update(canvas.pixels)
|
||||
// }
|
||||
|
||||
controls.mirror_x = new BlurredCheckbox (mirror_x_checkbox)
|
||||
controls.mirror_x.use = function(state){
|
||||
window.mirror_x = typeof state == "boolean" ? state : ! window.mirror_x
|
||||
this.update(window.mirror_x)
|
||||
}
|
||||
controls.mirror_y = new BlurredCheckbox (mirror_y_checkbox)
|
||||
controls.mirror_y.use = function(state){
|
||||
window.mirror_y = typeof state == "boolean" ? state : ! window.mirror_y
|
||||
this.update(window.mirror_y)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
controls.vertical = new BlurredCheckbox (vertical_checkbox)
|
||||
controls.vertical.memorable = true
|
||||
controls.vertical.use = function(state){
|
||||
canvas.vertical = typeof state == "boolean" ? state : ! canvas.vertical
|
||||
controls.vertical.refresh()
|
||||
}
|
||||
controls.vertical.refresh = function(){
|
||||
if (canvas.vertical) {
|
||||
document.body.classList.add("vertical")
|
||||
}
|
||||
else {
|
||||
document.body.classList.remove("vertical")
|
||||
}
|
||||
palette.repaint()
|
||||
letters.repaint()
|
||||
this.update(canvas.vertical)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
controls.brush_w = new Lex (brush_w_el)
|
||||
controls.brush_h = new Lex (brush_h_el)
|
||||
controls.canvas_w = new Lex (canvas_w_el)
|
||||
controls.canvas_h = new Lex (canvas_h_el)
|
||||
|
||||
// bind
|
||||
|
||||
controls.bind = function(){
|
||||
|
||||
for (var n in controls){
|
||||
var control = controls[n]
|
||||
if (typeof control === 'object' && 'bind' in control){
|
||||
control.bind()
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
controls.brush_w,
|
||||
controls.brush_h,
|
||||
controls.canvas_w,
|
||||
controls.canvas_h
|
||||
].forEach(function(lex){
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
lex.focus()
|
||||
if (is_mobile) cursor_input.focus()
|
||||
})
|
||||
});
|
||||
|
||||
controls.brush_w.key = keys.single_numeral_key(controls.brush_w, function(w){ brush.resize(w, brush.h) })
|
||||
controls.brush_w.raw_key = keys.arrow_key(function(w){ brush.size_add(w, 0) })
|
||||
|
||||
controls.brush_h.key = keys.single_numeral_key(controls.brush_h, function(h){ brush.resize(brush.w, h) })
|
||||
controls.brush_h.raw_key = keys.arrow_key(function(h){ brush.size_add(0, h) })
|
||||
|
||||
controls.canvas_w.key = keys.multi_numeral_key(controls.canvas_w, 3)
|
||||
controls.canvas_w.onBlur = keys.multi_numeral_blur(controls.canvas_w, function(w){ canvas.resize(w, canvas.h) })
|
||||
controls.canvas_w.raw_key = keys.arrow_key(function(w){ canvas.size_add(w, 0) })
|
||||
|
||||
controls.canvas_h.key = keys.multi_numeral_key(controls.canvas_h, 3)
|
||||
controls.canvas_h.onBlur = keys.multi_numeral_blur(controls.canvas_h, function(h){ canvas.resize(canvas.w, h) })
|
||||
controls.canvas_h.raw_key = keys.arrow_key(function(h){ canvas.size_add(0, h) })
|
||||
|
||||
add_custom_el.addEventListener("click", function(){
|
||||
custom.clone()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return controls
|
||||
})()
|
@ -0,0 +1,29 @@
|
||||
var custom = (function(){
|
||||
|
||||
var exports = {}
|
||||
|
||||
exports.clone = function (){
|
||||
var new_brush = brush.clone()
|
||||
var rapper = document.createElement("div")
|
||||
rapper.className = "custom"
|
||||
new_brush.append(rapper)
|
||||
custom_rapper.appendChild(rapper)
|
||||
// store in localstorage?
|
||||
rapper.addEventListener("click", function(e){
|
||||
if (e.shiftKey) {
|
||||
rapper.parentNode.removeChild(rapper)
|
||||
delete new_brush
|
||||
} else {
|
||||
// load this brush
|
||||
exports.load(new_brush)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.load = function(new_brush){
|
||||
brush.assign( new_brush )
|
||||
}
|
||||
|
||||
return exports
|
||||
|
||||
})()
|
@ -0,0 +1,213 @@
|
||||
var keys = (function(){
|
||||
|
||||
var keys = {}
|
||||
keys.bind = function(){
|
||||
cursor_input.addEventListener('keydown', function(e){
|
||||
|
||||
// console.log("keycode:", e.keyCode)
|
||||
if (e.altKey) {
|
||||
document.body.classList.add("dropper")
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 27: // esc
|
||||
if (!selection.hidden && current_canvas === canvas){
|
||||
selection.hide()
|
||||
selection.show()
|
||||
} else if (focused){
|
||||
focused.blur()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (window.focused && focused.raw_key) {
|
||||
focused.raw_key(e)
|
||||
return
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 219: // [
|
||||
if (current_tool.name != "text") {
|
||||
e.preventDefault()
|
||||
brush.contract(1)
|
||||
brush.modified = false
|
||||
check_if_lost_focus()
|
||||
}
|
||||
break
|
||||
case 221: // ]
|
||||
if (current_tool.name != "text") {
|
||||
e.preventDefault()
|
||||
brush.expand(1)
|
||||
brush.modified = false
|
||||
}
|
||||
break
|
||||
case 8: // backspace
|
||||
e.preventDefault()
|
||||
if (current_canvas === canvas)
|
||||
undo.new()
|
||||
current_canvas.focus_add(-1, 0)
|
||||
if (current_canvas === canvas)
|
||||
undo.save_focused_lex()
|
||||
focused.char = " "
|
||||
focused.build()
|
||||
return
|
||||
case 13: // return
|
||||
e.preventDefault()
|
||||
current_canvas.focusLex(focused.y, focused.x+1)
|
||||
return
|
||||
case 38: // up
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(0, -1)
|
||||
break
|
||||
case 40: // down
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(0, 1)
|
||||
break
|
||||
case 37: // left
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(-1, 0)
|
||||
break
|
||||
case 39: // right
|
||||
e.preventDefault()
|
||||
current_canvas.focus_add(1, 0)
|
||||
break
|
||||
// use typical windows and os x shortcuts
|
||||
// undo: ctrl-z or cmd-z
|
||||
// redo: ctrl-y or shift-cmd-z
|
||||
case 89: // y
|
||||
if (!e.ctrlKey && !e.metaKey) break;
|
||||
e.preventDefault();
|
||||
undo.redo();
|
||||
break
|
||||
case 90: // z
|
||||
if (!e.ctrlKey && !e.metaKey) break;
|
||||
e.preventDefault();
|
||||
if (e.shiftKey)
|
||||
undo.redo();
|
||||
else
|
||||
undo.undo();
|
||||
break
|
||||
// default:
|
||||
// if (focused) { focused.key(undefined, e.keyCode) }
|
||||
}
|
||||
})
|
||||
|
||||
cursor_input.addEventListener('input', function(e){
|
||||
/*
|
||||
if (! e.metaKey && ! e.ctrlKey && ! e.altKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
*/
|
||||
if (current_tool.name == "shader") {
|
||||
cursor_input.value = ""
|
||||
return
|
||||
}
|
||||
var char = cursor_input.value
|
||||
cursor_input.value = ""
|
||||
|
||||
// console.log("input:", char)
|
||||
|
||||
if (current_tool.name != "text" && ! brush.modified) {
|
||||
brush.char = char
|
||||
if (char == " ") {
|
||||
brush.bg = brush.fg
|
||||
}
|
||||
else if (brush.bg != fillColor) {
|
||||
brush.fg = brush.bg
|
||||
brush.bg = fillColor
|
||||
}
|
||||
brush.rebuild()
|
||||
}
|
||||
|
||||
if (focused && char) {
|
||||
var y = focused.y, x = focused.x
|
||||
if (current_canvas === canvas){
|
||||
undo.new()
|
||||
undo.save_focused_lex()
|
||||
}
|
||||
var moving = focused.key(char, e.keyCode)
|
||||
if ( ! moving || ! ('y' in focused && 'x' in focused) ) { return }
|
||||
current_canvas.focus_add(1, 0)
|
||||
}
|
||||
})
|
||||
|
||||
cursor_input.addEventListener("keyup", function(e){
|
||||
if (! e.altKey) {
|
||||
document.body.classList.remove("dropper")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
keys.int_key = function (f) {
|
||||
return function (key, keyCode) {
|
||||
var n = parseInt(key)
|
||||
! isNaN(n) && f(n)
|
||||
}
|
||||
}
|
||||
|
||||
keys.arrow_key = function (fn) {
|
||||
return function (e){
|
||||
switch (e.keyCode) {
|
||||
case 38: // up
|
||||
e.preventDefault()
|
||||
fn(1)
|
||||
break
|
||||
case 40: // down
|
||||
e.preventDefault()
|
||||
fn(-1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
keys.left_right_key = function (fn) {
|
||||
return function (e){
|
||||
switch (e.keyCode) {
|
||||
case 39: // right
|
||||
e.preventDefault()
|
||||
fn(1)
|
||||
break
|
||||
case 38: // up
|
||||
case 40: // down
|
||||
e.preventDefault()
|
||||
fn(0)
|
||||
break
|
||||
case 37: // left
|
||||
e.preventDefault()
|
||||
fn(-1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys.single_numeral_key = function (lex, fn) {
|
||||
return keys.int_key(function(n, keyCode){
|
||||
if (n == 0) n = 10
|
||||
lex.blur()
|
||||
fn(n)
|
||||
})
|
||||
}
|
||||
keys.multi_numeral_key = function (lex, digits){
|
||||
return keys.int_key(function(n, keyCode){
|
||||
lex.read()
|
||||
if (lex.char.length < digits) {
|
||||
n = parseInt(lex.char) * 10 + n
|
||||
}
|
||||
lex.char = ""+n
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
keys.multi_numeral_blur = function (lex, fn){
|
||||
return function(){
|
||||
var n = parseInt(lex.char)
|
||||
if (isNaN(n)) return
|
||||
fn(n)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
})()
|
||||
|
||||
function check_if_lost_focus() {
|
||||
if (! window.focused || ! window.focused.span)
|
||||
window.focused = canvas.aa[0][0]
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
var letters = (function(){
|
||||
|
||||
var last_charset = ""
|
||||
var charset_index = 0
|
||||
var charsets = [
|
||||
'Basic Latin',
|
||||
'Latin-1 Supplement',
|
||||
'Box Drawing',
|
||||
'Block Elements',
|
||||
]
|
||||
|
||||
var letters = new Matrix (1, 1, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
return lex
|
||||
})
|
||||
|
||||
letters.charset = ""
|
||||
|
||||
letters.repaint = function(charset){
|
||||
letters.charset = charset = charset || last_charset
|
||||
last_charset = charset
|
||||
var chars = unicode.block(charset, 32)
|
||||
if (chars[0] != " ") chars.unshift(" ")
|
||||
if (canvas.vertical) {
|
||||
letters.resize( Math.ceil( chars.length / 16 ), 16 )
|
||||
}
|
||||
else {
|
||||
letters.resize( 32, Math.ceil( chars.length / 32 ) )
|
||||
}
|
||||
|
||||
var i = 0
|
||||
|
||||
letters.forEach(function(lex,x,y){
|
||||
if (canvas.vertical) { x=x^y;y=x^y;x=x^y }
|
||||
var char = chars[i++]
|
||||
if (palette.chars.indexOf(brush.char) > 1) {
|
||||
lex.bg = brush.fg
|
||||
lex.fg = brush.bg
|
||||
}
|
||||
else {
|
||||
lex.bg = colors.black
|
||||
lex.fg = brush.fg == fillColor ? colors.black : brush.fg
|
||||
}
|
||||
lex.char = char
|
||||
lex.opacity = 1
|
||||
lex.build()
|
||||
})
|
||||
}
|
||||
|
||||
letters.bind = function(){
|
||||
letters.forEach(function(lex,x,y){
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
charset_index = (charset_index+1) % charsets.length
|
||||
letters.repaint(charsets[charset_index])
|
||||
return
|
||||
}
|
||||
else if (e.ctrlKey || e.which == 3) {
|
||||
brush.char = lex.char
|
||||
brush.bg = brush.fg
|
||||
brush.fg = fillColor
|
||||
}
|
||||
else {
|
||||
brush.char = lex.char
|
||||
if (lex.char == " ") {
|
||||
brush.bg = brush.fg
|
||||
}
|
||||
else if (brush.bg != fillColor) {
|
||||
brush.fg = brush.bg
|
||||
brush.bg = fillColor
|
||||
}
|
||||
}
|
||||
if (! brush.modified) {
|
||||
brush.generate()
|
||||
}
|
||||
palette.repaint()
|
||||
})
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return letters
|
||||
})()
|
@ -0,0 +1,116 @@
|
||||
var palette = (function(){
|
||||
|
||||
var palette = new Matrix (32, 2, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
return lex
|
||||
})
|
||||
|
||||
var palette_index = localStorage.getItem("ascii.palette") || 1
|
||||
var palette_list = [all_hue, all_inv_hue, mirc_color, mirc_color_reverse]
|
||||
var palette_fn = palette_list[palette_index]
|
||||
var dither = {
|
||||
aa: '▓▒░ ',
|
||||
a: '▓',
|
||||
b: '▒',
|
||||
c: '░',
|
||||
d: ' ',
|
||||
p: function(n){
|
||||
return dither.aa[Math.floor(Math.abs(n) % 4)]
|
||||
}
|
||||
}
|
||||
palette.chars = " " + dither.a + dither.b + dither.c
|
||||
|
||||
palette.repaint = function(){
|
||||
var xw = use_experimental_palette ? 5 : 2
|
||||
if (canvas.vertical) {
|
||||
palette.resize( xw, 16 )
|
||||
}
|
||||
else {
|
||||
palette.resize( 32, xw )
|
||||
}
|
||||
|
||||
palette.forEach(function(lex,x,y){
|
||||
if (canvas.vertical) { x=x^y;y=x^y;x=x^y;x*=2 }
|
||||
if (y < 2) {
|
||||
lex.bg = palette_fn(x>>1)
|
||||
lex.fg = palette_fn(x>>1)
|
||||
}
|
||||
else {
|
||||
lex.bg = fillColor
|
||||
lex.fg = palette_fn(x>>1)
|
||||
}
|
||||
lex.char = palette.chars[y]
|
||||
lex.opacity = 1
|
||||
lex.build()
|
||||
if (lex.char == "_") lex.char = " "
|
||||
})
|
||||
}
|
||||
palette.repaint()
|
||||
var use_experimental_palette = false
|
||||
palette.experimental = function(state){
|
||||
use_experimental_palette = typeof state == "boolean" ? state : ! use_experimental_palette
|
||||
use_experimental_palette ? palette.resize(32, 5) : palette.resize(32, 2)
|
||||
palette.repaint()
|
||||
return use_experimental_palette
|
||||
}
|
||||
|
||||
palette.bind = function(){
|
||||
palette.forEach(function(lex, x, y){
|
||||
if (lex.bound) return
|
||||
lex.bound = true
|
||||
|
||||
lex.span.addEventListener('mousedown', function(e){
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
palette_index = (palette_index+1) % palette_list.length
|
||||
localStorage.setItem("ascii.palette", palette_index)
|
||||
palette_fn = palette_list[palette_index]
|
||||
palette.repaint()
|
||||
return
|
||||
}
|
||||
if (e.ctrlKey || e.which == 3) return
|
||||
if (brush.char == " " && lex.char == " ") {
|
||||
brush.fg = lex.fg
|
||||
brush.bg = lex.bg
|
||||
brush.char = lex.char
|
||||
}
|
||||
else if (lex.char != " ") {
|
||||
brush.fg = lex.bg
|
||||
brush.bg = lex.fg
|
||||
brush.char = lex.char
|
||||
}
|
||||
else {
|
||||
brush.fg = lex.bg
|
||||
brush.bg = fillColor
|
||||
// brush.char = lex.char
|
||||
}
|
||||
brush.opacity = lex.opacity
|
||||
if (! brush.modified) {
|
||||
brush.generate()
|
||||
}
|
||||
if (filling || e.ctrlKey) {
|
||||
fillColor = lex.bg
|
||||
}
|
||||
letters.repaint()
|
||||
})
|
||||
|
||||
lex.span.addEventListener('contextmenu', function(e){
|
||||
e.preventDefault()
|
||||
fillColor = y ? lex.fg : lex.bg
|
||||
palette.repaint()
|
||||
brush.fg = lex.fg
|
||||
brush.char = lex.char
|
||||
brush.opacity = lex.opacity
|
||||
brush.generate()
|
||||
brush_rapper.style.borderColor = css_reverse_lookup[fillColor]
|
||||
return
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
brush_rapper.style.borderColor = css_reverse_lookup[fillColor]
|
||||
|
||||
return palette
|
||||
|
||||
})()
|
@ -0,0 +1,159 @@
|
||||
var selection = (function(){
|
||||
|
||||
var creating = false, moving = false, copying = false
|
||||
|
||||
var selection_canvas = new Matrix (1, 1, function(x,y){
|
||||
var lex = new Lex (x,y)
|
||||
lex.build()
|
||||
return lex
|
||||
})
|
||||
|
||||
var selector_el = document.createElement("div")
|
||||
selector_el.className = "selector_el"
|
||||
selection_canvas.append(selector_el)
|
||||
document.body.appendChild(selector_el)
|
||||
|
||||
// in selection mode..
|
||||
// - we start by clicking the canvas. this positions the selection, and copies
|
||||
// the character
|
||||
// - then we drag down and to the right. this resizes the selection and pushes new
|
||||
// rows and columns. each of these copies the character underneath.
|
||||
// - on mouseup, the selection is locked. then..
|
||||
// - drag the selection to move it -- this "cuts" it and leaves a blank space on the canvas.
|
||||
// - shift-drag the selection to copy it
|
||||
|
||||
var a = [0, 0]
|
||||
var b = [0, 0]
|
||||
var c = [0, 0]
|
||||
var d = [0, 0]
|
||||
|
||||
function reset () {
|
||||
a[0] = a[1] = b[0] = b[1] = 0
|
||||
}
|
||||
function left (a,b) { return min(a[0],b[0]) }
|
||||
function top (a,b) { return min(a[1],b[1]) }
|
||||
function right (a,b) { return max(a[0],b[0]) }
|
||||
function bottom (a,b) { return max(a[1],b[1]) }
|
||||
function width (a,b) { return abs(a[0]-b[0])+1 }
|
||||
function height (a,b) { return abs(a[1]-b[1])+1 }
|
||||
function mag_x (a,b) { return a[0]-b[0] }
|
||||
function mag_y (a,b) { return a[1]-b[1] }
|
||||
function orient (a,b) {
|
||||
var l = left(a,b), m = top(a,b), n = right(a,b), o = bottom(a,b)
|
||||
a[0] = l ; a[1] = m ; b[0] = n ; b[1] = o
|
||||
}
|
||||
|
||||
function contains (a,b,point) {
|
||||
var contains_x = a[0] <= point[0] && point[0] <= b[0]
|
||||
var contains_y = a[1] <= point[1] && point[1] <= b[1]
|
||||
return (contains_x && contains_y)
|
||||
}
|
||||
function reposition (aa, bb) {
|
||||
aa = aa || a
|
||||
bb = bb || b
|
||||
var cell = canvas.aa[top(aa, bb)][left(aa, bb)].span
|
||||
var cell_left = cell.offsetLeft
|
||||
var cell_top = cell.offsetTop
|
||||
var cell_width = cell.offsetWidth
|
||||
var cell_height = cell.offsetHeight
|
||||
|
||||
var w = width(aa, bb)
|
||||
var h = height(aa, bb)
|
||||
|
||||
selector_el.style.top = (cell_top-1) + "px"
|
||||
selector_el.style.left = (cell_left-1) + "px"
|
||||
selector_el.style.width = (cell_width*w+1) + "px"
|
||||
selector_el.style.height = (cell_height*h+1) + "px"
|
||||
}
|
||||
function down (e, lex, point){
|
||||
if ( ! contains(a,b,point) ) {
|
||||
copying = false
|
||||
moving = false
|
||||
creating = true
|
||||
a[0] = point[0]
|
||||
a[1] = point[1]
|
||||
b[0] = point[0]
|
||||
b[1] = point[1]
|
||||
reposition(a,b)
|
||||
selection.hidden = false
|
||||
selector_el.classList.add("creating")
|
||||
} else {
|
||||
copying = false
|
||||
moving = true
|
||||
creating = false
|
||||
c[0] = point[0]
|
||||
c[1] = point[1]
|
||||
d[0] = point[0]
|
||||
d[1] = point[1]
|
||||
}
|
||||
show()
|
||||
selector_el.classList.remove("dragging")
|
||||
}
|
||||
function move (e, lex, point){
|
||||
if (creating) {
|
||||
b[0] = point[0]
|
||||
b[1] = point[1]
|
||||
reposition(a,b)
|
||||
}
|
||||
else if (moving) {
|
||||
d[0] = point[0]
|
||||
d[1] = point[1]
|
||||
var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] )
|
||||
var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] )
|
||||
reposition( [ a[0] + dx, a[1] + dy ], [ b[0] + dx, b[1] + dy ])
|
||||
}
|
||||
else if (copying) {
|
||||
}
|
||||
}
|
||||
function up (e) {
|
||||
if (creating) {
|
||||
orient(a,b)
|
||||
selection_canvas.resize(width(a,b), height(a,b))
|
||||
reposition(a,b)
|
||||
blit.copy_from( canvas, selection_canvas, a[0], a[1] )
|
||||
selection_canvas.build()
|
||||
selector_el.classList.remove("creating")
|
||||
}
|
||||
if (moving) {
|
||||
var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] )
|
||||
var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] )
|
||||
a[0] += dx
|
||||
a[1] += dy
|
||||
b[0] += dx
|
||||
b[1] += dy
|
||||
undo.new()
|
||||
undo.save_rect(a[0], a[1], b[0] - a[0] + 1, b[1] - a[1] + 1)
|
||||
blit.copy_to( canvas, selection_canvas, a[0], a[1] )
|
||||
}
|
||||
if (copying) {
|
||||
}
|
||||
creating = moving = copying = false
|
||||
selector_el.classList.remove("dragging")
|
||||
}
|
||||
|
||||
function show () {
|
||||
selecting = true
|
||||
}
|
||||
function hide () {
|
||||
reset()
|
||||
selector_el.style.top = "-9999px"
|
||||
selector_el.style.left = "-9999px"
|
||||
selector_el.style.width = "0px"
|
||||
selector_el.style.height = "0px"
|
||||
creating = moving = copying = false
|
||||
selection.hidden = true
|
||||
selecting = false
|
||||
}
|
||||
|
||||
var selection = {}
|
||||
selection.reposition = reposition
|
||||
selection.down = down
|
||||
selection.move = move
|
||||
selection.up = up
|
||||
selection.canvas = selection_canvas
|
||||
selection.show = show
|
||||
selection.hide = hide
|
||||
selection.hidden = true
|
||||
return selection
|
||||
|
||||
})()
|
@ -0,0 +1,170 @@
|
||||
var Tool = Model({
|
||||
init: function (el) {
|
||||
this.el = el
|
||||
this.lex = new Lex (el)
|
||||
this.name = el.innerHTML
|
||||
},
|
||||
bind: function(){
|
||||
var tool = this
|
||||
tool.el.addEventListener('mousedown', function(e){
|
||||
tool.focus()
|
||||
})
|
||||
tool.el.addEventListener('contextmenu', function(e){
|
||||
tool.context(e)
|
||||
})
|
||||
if (tool.memorable) {
|
||||
// console.log(tool.name, localStorage.getItem("ascii.tools." + tool.name) )
|
||||
tool.use( localStorage.getItem("ascii.tools." + tool.name) == "true" )
|
||||
}
|
||||
},
|
||||
use: function(){},
|
||||
context: function(e){},
|
||||
done: function(){},
|
||||
focus: function(){
|
||||
// focused && focused.blur()
|
||||
current_tool && current_tool.blur()
|
||||
current_tool = this
|
||||
this.el.classList.add('focused')
|
||||
this.use()
|
||||
if (this.name != 'shader' && is_desktop) { cursor_input.focus() }
|
||||
},
|
||||
blur: function(){
|
||||
current_tool = null
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var FileTool = Tool.extend({
|
||||
focus: function(){
|
||||
if (current_filetool === this) {
|
||||
this.blur()
|
||||
return
|
||||
}
|
||||
current_filetool && current_filetool.blur()
|
||||
current_filetool = this
|
||||
this.el.classList.add('focused')
|
||||
this.use()
|
||||
if (this.name != 'shader' && is_desktop) { cursor_input.focus() }
|
||||
},
|
||||
blur: function(){
|
||||
current_filetool = null
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var RadioItem = Tool.extend({
|
||||
init: function(group, el){
|
||||
this.group = group
|
||||
this.el = el
|
||||
},
|
||||
focus: function(){
|
||||
this.el.classList.add('focused')
|
||||
},
|
||||
blur: function(){
|
||||
this.el.classList.remove('focused')
|
||||
},
|
||||
bind: function(){
|
||||
var control = this
|
||||
this.el.addEventListener('mousedown', function(){
|
||||
control.group.use(control)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var RadioGroup = Tool.extend({
|
||||
init: function(el){
|
||||
this.el = el
|
||||
this.controls = {}
|
||||
var names = el.innerHTML.split(' ')
|
||||
el.innerHTML = ''
|
||||
var group = this
|
||||
names.forEach(function(value){
|
||||
var el = document.createElement('span')
|
||||
el.classList.add('radio','tool')
|
||||
var control = new RadioItem(group, el)
|
||||
if (value.substr(0,1) === '*') {
|
||||
control.value = value = value.substr(1)
|
||||
group.use(control)
|
||||
}
|
||||
control.value = el.innerHTML = value
|
||||
group.controls[value] = control
|
||||
group.el.appendChild(el)
|
||||
})
|
||||
},
|
||||
use: function(control){
|
||||
if (typeof control === 'string') {
|
||||
control = this.controls[control]
|
||||
}
|
||||
this.selected_control && this.selected_control.blur()
|
||||
this.value = control.value
|
||||
this.selected_control = control
|
||||
control.focus()
|
||||
control.use()
|
||||
if (this.memorable){
|
||||
localStorage.setItem("ascii.tools." + this.name, this.value)
|
||||
}
|
||||
},
|
||||
bind: function(){
|
||||
var tool = this
|
||||
for (var n in this.controls){
|
||||
this.controls[n].bind()
|
||||
}
|
||||
if (tool.memorable) {
|
||||
var value = localStorage.getItem("ascii.tools." + tool.name)
|
||||
if (value) tool.use(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var Checkbox = Tool.extend({
|
||||
init: function (el){
|
||||
this.__init(el)
|
||||
var name = this.name.replace(/^[x_] /,"")
|
||||
var state = localStorage.getItem("ascii.tools." + name) == "true" || this.name[0] == "x"
|
||||
this.name = name
|
||||
this.update(state)
|
||||
},
|
||||
update: function(state){
|
||||
if (state) this.el.innerHTML = "x " + this.name
|
||||
else this.el.innerHTML = "_ " + this.name
|
||||
if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) }
|
||||
}
|
||||
})
|
||||
|
||||
var BlurredCheckbox = Checkbox.extend({
|
||||
focus: function(){
|
||||
this.use()
|
||||
},
|
||||
blur: function(){
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var BlurredTool = Tool.extend({
|
||||
focus: function(){
|
||||
this.use()
|
||||
},
|
||||
blur: function(){
|
||||
this.el.classList.remove('focused')
|
||||
this.done()
|
||||
}
|
||||
})
|
||||
|
||||
var HiddenCheckbox = BlurredCheckbox.extend({
|
||||
on: "o",
|
||||
off: ".",
|
||||
init: function (el){
|
||||
this.el = el
|
||||
this.lex = new Lex (el)
|
||||
this.name = this.el.id
|
||||
var state = localStorage.getItem("ascii.tools." + name) == "true" || this.el.innerHTML[0] == this.on
|
||||
this.update(state)
|
||||
},
|
||||
update: function(state){
|
||||
this.el.innerHTML = state ? this.on : this.off
|
||||
if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) }
|
||||
}
|
||||
})
|
@ -0,0 +1,176 @@
|
||||
var transform = (function(){
|
||||
|
||||
var p = [0,0], q = [0,0]
|
||||
var mode
|
||||
var copy
|
||||
|
||||
function down (e, lex, point){
|
||||
p[0] = point[0]
|
||||
p[1] = point[1]
|
||||
q[0] = e.pageX
|
||||
q[1] = e.pageY
|
||||
undo.new()
|
||||
undo.save_rect(0, 0, canvas.w, canvas.h)
|
||||
copy = canvas.clone()
|
||||
mode.init(e)
|
||||
}
|
||||
function move (e, lex, point){
|
||||
var pdx = point[0] - p[0]
|
||||
var pdy = point[1] - p[1]
|
||||
var dx = e.pageX - q[0]
|
||||
var dy = e.pageY - q[1]
|
||||
var w = canvas.w
|
||||
var h = canvas.h
|
||||
mode.before(dx, dy, pdx, pdy, point)
|
||||
for (var x = 0; x < w; x++) {
|
||||
for (var y = 0; y < h; y++) {
|
||||
lex = canvas.get(x, y)
|
||||
if (! mode.shade( copy, canvas, lex, x, y, w, h )) {
|
||||
lex.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function up (e){
|
||||
}
|
||||
|
||||
var modes = {
|
||||
|
||||
rotate: {
|
||||
init: function(e){
|
||||
mode.theta = 0
|
||||
},
|
||||
before: function(dx, dy){
|
||||
var radius = dist(0, 0, dx, dy)
|
||||
if (radius < 10) return
|
||||
mode.theta = angle(0, 0, dx, -dy)
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
x = (x/w) * 2 - 1
|
||||
y = (y/h) * 2 - 1
|
||||
var ca = cos(mode.theta)
|
||||
var sa = sin(mode.theta)
|
||||
var a = x * ca - y * sa
|
||||
var b = x * sa + y * ca
|
||||
x = (a + 1) / 2 * w
|
||||
y = (b + 1) / 2 * h
|
||||
var copy = src.get(x, y)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
scale: {
|
||||
init: function(e){
|
||||
mode.independent = e.shiftKey || e.altKey || e.metaKey
|
||||
mode.x_scale = mode.y_scale = 0
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy){
|
||||
if (mode.independent) {
|
||||
mode.x_scale = Math.pow(2, -pdx / (canvas.w / 8))
|
||||
mode.y_scale = Math.pow(2, -pdy / (canvas.h / 8))
|
||||
}
|
||||
else {
|
||||
mode.x_scale = mode.y_scale = Math.pow(2, -pdx / (canvas.w / 8))
|
||||
}
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
x = ((x-p[0])/w) * 2 - 1
|
||||
y = ((y-p[1])/h) * 2 - 1
|
||||
x *= mode.x_scale
|
||||
y *= mode.y_scale
|
||||
x = (x + 1) / 2 * w
|
||||
y = (y + 1) / 2 * h
|
||||
var copy = src.get(x+p[0], y+p[1])
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
translate: {
|
||||
init: function(e){
|
||||
mode.dx = mode.dy = 0
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy){
|
||||
mode.dx = -pdx
|
||||
mode.dy = -pdy
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
var copy = src.get(x+mode.dx, y+mode.dy)
|
||||
lex.assign(copy)
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
slice: {
|
||||
init: function(e){
|
||||
mode.is_y = ! (e.altKey || e.metaKey)
|
||||
mode.reverse = !! (e.shiftKey)
|
||||
mode.position = 0
|
||||
mode.direction = 0
|
||||
mode.last_dd = -1
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy, point){
|
||||
var new_position = mode.is_y ? point[1] : point[0]
|
||||
var dd = mode.is_y ? pdx : pdy
|
||||
|
||||
if (mode.position !== new_position) {
|
||||
mode.position = new_position
|
||||
mode.direction = 0
|
||||
}
|
||||
if (mode.last_dd !== -1) {
|
||||
mode.direction = mode.last_dd - dd
|
||||
}
|
||||
console.log(mode.position)
|
||||
mode.last_dd = dd
|
||||
copy.assign(canvas)
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
if (mode.is_y) {
|
||||
if (y >= mode.position || (mode.reverse && mode.position >= y)) {
|
||||
var copy = src.get(x + mode.direction, y)
|
||||
lex.assign(copy)
|
||||
}
|
||||
}
|
||||
else if (x >= mode.position || (mode.reverse && mode.position >= x)) {
|
||||
var copy = src.get(x, y + mode.direction)
|
||||
lex.assign(copy)
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
/*
|
||||
mode: {
|
||||
init: function(e){
|
||||
},
|
||||
before: function(dx, dy, pdx, pdy){
|
||||
},
|
||||
shade: function(src, dest, lex, x, y, w, h){
|
||||
},
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
function set_mode(m){
|
||||
if (m in modes) {
|
||||
mode = modes[m]
|
||||
transforming = true
|
||||
}
|
||||
}
|
||||
|
||||
function done(){
|
||||
transforming = false
|
||||
copy && copy.demolish()
|
||||
}
|
||||
|
||||
return {
|
||||
down: down,
|
||||
move: move,
|
||||
up: up,
|
||||
set_mode: set_mode,
|
||||
modes: modes,
|
||||
done: done,
|
||||
}
|
||||
|
||||
})()
|
@ -0,0 +1,29 @@
|
||||
Jollo LNT license
|
||||
Version 1 - February 2015
|
||||
|
||||
Copyright, 2015. JOLLO NET NA.
|
||||
The Jollo IRC Network. <//jollo.org/>
|
||||
|
||||
Vu, fare wanderer, confronted with raw, programmatic instruction
|
||||
dans la forme la plus pure. A hesitation, troubled to the terms
|
||||
qui ce license affirme. Par un voyage du explorer le mechanisme
|
||||
et ponder la fabrication. Voila! La remide: egress sans risque.
|
||||
|
||||
Sans trace (Leave No Trace) via sept principales:
|
||||
|
||||
0. Modifique language en advance. L'Apposer Jollo LNT license
|
||||
with copies en distribuer.
|
||||
|
||||
1. Non responsible pour neglige programme du problematique.
|
||||
|
||||
2. Non sympathie pour neglige programme du problematique.
|
||||
|
||||
3. Non permission l'modifique under any circumstance.
|
||||
|
||||
4. Non permission distribution under any circumstance.
|
||||
|
||||
5. Respect les programmatic instructions.
|
||||
|
||||
6. Non interfere avec l'harmonie d'une amitie.
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
1) document: constraints for video reverse
|
||||
2) document: Shift + LMB to delete brush, Shift + LMB to switch character sets
|
||||
3) feature: allow to adjust cutoff warning threshold, {dis,en}able cutoff & print line numbers affected
|
||||
4) feature: {bold,italic,underline} attributes
|
||||
5) feature: keyboard instead of [LR]MB
|
||||
6) feature: {line,measuring} tool
|
||||
7) feature: status bar w/ position, etc.
|
||||
8) feature: zoom {in,out}
|
||||
9) reimplement: {save,upload} as PNG & gallery
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exports the ExposedJsApi.java object if available, otherwise exports the PromptBasedNativeApi.
|
||||
*/
|
||||
|
||||
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
|
||||
var currentApi = nativeApi;
|
||||
|
||||
module.exports = {
|
||||
get: function() { return currentApi; },
|
||||
setPreferPrompt: function(value) {
|
||||
currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi;
|
||||
},
|
||||
// Used only by tests.
|
||||
set: function(value) {
|
||||
currentApi = value;
|
||||
}
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
|
||||
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
|
||||
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
|
||||
},
|
||||
setNativeToJsBridgeMode: function(bridgeSecret, value) {
|
||||
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
|
||||
},
|
||||
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
|
||||
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
|
||||
}
|
||||
};
|
286
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova-js-src/exec.js
vendored
Normal file
286
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova-js-src/exec.js
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Execute a cordova command. It is up to the native side whether this action
|
||||
* is synchronous or asynchronous. The native side can return:
|
||||
* Synchronous: PluginResult object as a JSON string
|
||||
* Asynchronous: Empty string ""
|
||||
* If async, the native side will cordova.callbackSuccess or cordova.callbackError,
|
||||
* depending upon the result of the action.
|
||||
*
|
||||
* @param {Function} success The success callback
|
||||
* @param {Function} fail The fail callback
|
||||
* @param {String} service The name of the service to use
|
||||
* @param {String} action Action to be run in cordova
|
||||
* @param {String[]} [args] Zero or more arguments to pass to the method
|
||||
*/
|
||||
var cordova = require('cordova'),
|
||||
nativeApiProvider = require('cordova/android/nativeapiprovider'),
|
||||
utils = require('cordova/utils'),
|
||||
base64 = require('cordova/base64'),
|
||||
channel = require('cordova/channel'),
|
||||
jsToNativeModes = {
|
||||
PROMPT: 0,
|
||||
JS_OBJECT: 1
|
||||
},
|
||||
nativeToJsModes = {
|
||||
// Polls for messages using the JS->Native bridge.
|
||||
POLLING: 0,
|
||||
// For LOAD_URL to be viable, it would need to have a work-around for
|
||||
// the bug where the soft-keyboard gets dismissed when a message is sent.
|
||||
LOAD_URL: 1,
|
||||
// For the ONLINE_EVENT to be viable, it would need to intercept all event
|
||||
// listeners (both through addEventListener and window.ononline) as well
|
||||
// as set the navigator property itself.
|
||||
ONLINE_EVENT: 2,
|
||||
EVAL_BRIDGE: 3
|
||||
},
|
||||
jsToNativeBridgeMode, // Set lazily.
|
||||
nativeToJsBridgeMode = nativeToJsModes.EVAL_BRIDGE,
|
||||
pollEnabled = false,
|
||||
bridgeSecret = -1;
|
||||
|
||||
var messagesFromNative = [];
|
||||
var isProcessing = false;
|
||||
var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve();
|
||||
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); };
|
||||
|
||||
function androidExec(success, fail, service, action, args) {
|
||||
if (bridgeSecret < 0) {
|
||||
// If we ever catch this firing, we'll need to queue up exec()s
|
||||
// and fire them once we get a secret. For now, I don't think
|
||||
// it's possible for exec() to be called since plugins are parsed but
|
||||
// not run until until after onNativeReady.
|
||||
throw new Error('exec() called without bridgeSecret');
|
||||
}
|
||||
// Set default bridge modes if they have not already been set.
|
||||
// By default, we use the failsafe, since addJavascriptInterface breaks too often
|
||||
if (jsToNativeBridgeMode === undefined) {
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||
}
|
||||
|
||||
// If args is not provided, default to an empty array
|
||||
args = args || [];
|
||||
|
||||
// Process any ArrayBuffers in the args into a string.
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (utils.typeName(args[i]) == 'ArrayBuffer') {
|
||||
args[i] = base64.fromArrayBuffer(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var callbackId = service + cordova.callbackId++,
|
||||
argsJson = JSON.stringify(args);
|
||||
if (success || fail) {
|
||||
cordova.callbacks[callbackId] = {success:success, fail:fail};
|
||||
}
|
||||
|
||||
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
|
||||
// If argsJson was received by Java as null, try again with the PROMPT bridge mode.
|
||||
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
|
||||
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") {
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
|
||||
androidExec(success, fail, service, action, args);
|
||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||
} else if (msgs) {
|
||||
messagesFromNative.push(msgs);
|
||||
// Always process async to avoid exceptions messing up stack.
|
||||
nextTick(processMessages);
|
||||
}
|
||||
}
|
||||
|
||||
androidExec.init = function() {
|
||||
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode);
|
||||
channel.onNativeReady.fire();
|
||||
};
|
||||
|
||||
function pollOnceFromOnlineEvent() {
|
||||
pollOnce(true);
|
||||
}
|
||||
|
||||
function pollOnce(opt_fromOnlineEvent) {
|
||||
if (bridgeSecret < 0) {
|
||||
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions.
|
||||
// We know there's nothing to retrieve, so no need to poll.
|
||||
return;
|
||||
}
|
||||
var msgs = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent);
|
||||
if (msgs) {
|
||||
messagesFromNative.push(msgs);
|
||||
// Process sync since we know we're already top-of-stack.
|
||||
processMessages();
|
||||
}
|
||||
}
|
||||
|
||||
function pollingTimerFunc() {
|
||||
if (pollEnabled) {
|
||||
pollOnce();
|
||||
setTimeout(pollingTimerFunc, 50);
|
||||
}
|
||||
}
|
||||
|
||||
function hookOnlineApis() {
|
||||
function proxyEvent(e) {
|
||||
cordova.fireWindowEvent(e.type);
|
||||
}
|
||||
// The network module takes care of firing online and offline events.
|
||||
// It currently fires them only on document though, so we bridge them
|
||||
// to window here (while first listening for exec()-releated online/offline
|
||||
// events).
|
||||
window.addEventListener('online', pollOnceFromOnlineEvent, false);
|
||||
window.addEventListener('offline', pollOnceFromOnlineEvent, false);
|
||||
cordova.addWindowEventHandler('online');
|
||||
cordova.addWindowEventHandler('offline');
|
||||
document.addEventListener('online', proxyEvent, false);
|
||||
document.addEventListener('offline', proxyEvent, false);
|
||||
}
|
||||
|
||||
hookOnlineApis();
|
||||
|
||||
androidExec.jsToNativeModes = jsToNativeModes;
|
||||
androidExec.nativeToJsModes = nativeToJsModes;
|
||||
|
||||
androidExec.setJsToNativeBridgeMode = function(mode) {
|
||||
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
|
||||
mode = jsToNativeModes.PROMPT;
|
||||
}
|
||||
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
|
||||
jsToNativeBridgeMode = mode;
|
||||
};
|
||||
|
||||
androidExec.setNativeToJsBridgeMode = function(mode) {
|
||||
if (mode == nativeToJsBridgeMode) {
|
||||
return;
|
||||
}
|
||||
if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
|
||||
pollEnabled = false;
|
||||
}
|
||||
|
||||
nativeToJsBridgeMode = mode;
|
||||
// Tell the native side to switch modes.
|
||||
// Otherwise, it will be set by androidExec.init()
|
||||
if (bridgeSecret >= 0) {
|
||||
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode);
|
||||
}
|
||||
|
||||
if (mode == nativeToJsModes.POLLING) {
|
||||
pollEnabled = true;
|
||||
setTimeout(pollingTimerFunc, 1);
|
||||
}
|
||||
};
|
||||
|
||||
function buildPayload(payload, message) {
|
||||
var payloadKind = message.charAt(0);
|
||||
if (payloadKind == 's') {
|
||||
payload.push(message.slice(1));
|
||||
} else if (payloadKind == 't') {
|
||||
payload.push(true);
|
||||
} else if (payloadKind == 'f') {
|
||||
payload.push(false);
|
||||
} else if (payloadKind == 'N') {
|
||||
payload.push(null);
|
||||
} else if (payloadKind == 'n') {
|
||||
payload.push(+message.slice(1));
|
||||
} else if (payloadKind == 'A') {
|
||||
var data = message.slice(1);
|
||||
payload.push(base64.toArrayBuffer(data));
|
||||
} else if (payloadKind == 'S') {
|
||||
payload.push(window.atob(message.slice(1)));
|
||||
} else if (payloadKind == 'M') {
|
||||
var multipartMessages = message.slice(1);
|
||||
while (multipartMessages !== "") {
|
||||
var spaceIdx = multipartMessages.indexOf(' ');
|
||||
var msgLen = +multipartMessages.slice(0, spaceIdx);
|
||||
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
|
||||
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
|
||||
buildPayload(payload, multipartMessage);
|
||||
}
|
||||
} else {
|
||||
payload.push(JSON.parse(message));
|
||||
}
|
||||
}
|
||||
|
||||
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
|
||||
function processMessage(message) {
|
||||
var firstChar = message.charAt(0);
|
||||
if (firstChar == 'J') {
|
||||
// This is deprecated on the .java side. It doesn't work with CSP enabled.
|
||||
eval(message.slice(1));
|
||||
} else if (firstChar == 'S' || firstChar == 'F') {
|
||||
var success = firstChar == 'S';
|
||||
var keepCallback = message.charAt(1) == '1';
|
||||
var spaceIdx = message.indexOf(' ', 2);
|
||||
var status = +message.slice(2, spaceIdx);
|
||||
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
|
||||
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
|
||||
var payloadMessage = message.slice(nextSpaceIdx + 1);
|
||||
var payload = [];
|
||||
buildPayload(payload, payloadMessage);
|
||||
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
|
||||
} else {
|
||||
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function processMessages() {
|
||||
// Check for the reentrant case.
|
||||
if (isProcessing) {
|
||||
return;
|
||||
}
|
||||
if (messagesFromNative.length === 0) {
|
||||
return;
|
||||
}
|
||||
isProcessing = true;
|
||||
try {
|
||||
var msg = popMessageFromQueue();
|
||||
// The Java side can send a * message to indicate that it
|
||||
// still has messages waiting to be retrieved.
|
||||
if (msg == '*' && messagesFromNative.length === 0) {
|
||||
nextTick(pollOnce);
|
||||
return;
|
||||
}
|
||||
processMessage(msg);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
if (messagesFromNative.length > 0) {
|
||||
nextTick(processMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function popMessageFromQueue() {
|
||||
var messageBatch = messagesFromNative.shift();
|
||||
if (messageBatch == '*') {
|
||||
return '*';
|
||||
}
|
||||
|
||||
var spaceIdx = messageBatch.indexOf(' ');
|
||||
var msgLen = +messageBatch.slice(0, spaceIdx);
|
||||
var message = messageBatch.substr(spaceIdx + 1, msgLen);
|
||||
messageBatch = messageBatch.slice(spaceIdx + msgLen + 1);
|
||||
if (messageBatch) {
|
||||
messagesFromNative.unshift(messageBatch);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
module.exports = androidExec;
|
125
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova-js-src/platform.js
vendored
Normal file
125
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova-js-src/platform.js
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// The last resume event that was received that had the result of a plugin call.
|
||||
var lastResumeEvent = null;
|
||||
|
||||
module.exports = {
|
||||
id: 'android',
|
||||
bootstrap: function() {
|
||||
var channel = require('cordova/channel'),
|
||||
cordova = require('cordova'),
|
||||
exec = require('cordova/exec'),
|
||||
modulemapper = require('cordova/modulemapper');
|
||||
|
||||
// Get the shared secret needed to use the bridge.
|
||||
exec.init();
|
||||
|
||||
// TODO: Extract this as a proper plugin.
|
||||
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
|
||||
|
||||
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||
|
||||
// Inject a listener for the backbutton on the document.
|
||||
var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
|
||||
backButtonChannel.onHasSubscribersChange = function() {
|
||||
// If we just attached the first handler or detached the last handler,
|
||||
// let native know we need to override the back button.
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
|
||||
};
|
||||
|
||||
// Add hardware MENU and SEARCH button handlers
|
||||
cordova.addDocumentEventHandler('menubutton');
|
||||
cordova.addDocumentEventHandler('searchbutton');
|
||||
|
||||
function bindButtonChannel(buttonName) {
|
||||
// generic button bind used for volumeup/volumedown buttons
|
||||
var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
|
||||
volumeButtonChannel.onHasSubscribersChange = function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
|
||||
};
|
||||
}
|
||||
// Inject a listener for the volume buttons on the document.
|
||||
bindButtonChannel('volumeup');
|
||||
bindButtonChannel('volumedown');
|
||||
|
||||
// The resume event is not "sticky", but it is possible that the event
|
||||
// will contain the result of a plugin call. We need to ensure that the
|
||||
// plugin result is delivered even after the event is fired (CB-10498)
|
||||
var cordovaAddEventListener = document.addEventListener;
|
||||
|
||||
document.addEventListener = function(evt, handler, capture) {
|
||||
cordovaAddEventListener(evt, handler, capture);
|
||||
|
||||
if (evt === 'resume' && lastResumeEvent) {
|
||||
handler(lastResumeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
// Let native code know we are all done on the JS side.
|
||||
// Native code will then un-hide the WebView.
|
||||
channel.onCordovaReady.subscribe(function() {
|
||||
exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
|
||||
exec(null, null, APP_PLUGIN_NAME, "show", []);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function onMessageFromNative(msg) {
|
||||
var cordova = require('cordova');
|
||||
var action = msg.action;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
// Button events
|
||||
case 'backbutton':
|
||||
case 'menubutton':
|
||||
case 'searchbutton':
|
||||
// App life cycle events
|
||||
case 'pause':
|
||||
// Volume events
|
||||
case 'volumedownbutton':
|
||||
case 'volumeupbutton':
|
||||
cordova.fireDocumentEvent(action);
|
||||
break;
|
||||
case 'resume':
|
||||
if(arguments.length > 1 && msg.pendingResult) {
|
||||
if(arguments.length === 2) {
|
||||
msg.pendingResult.result = arguments[1];
|
||||
} else {
|
||||
// The plugin returned a multipart message
|
||||
var res = [];
|
||||
for(var i = 1; i < arguments.length; i++) {
|
||||
res.push(arguments[i]);
|
||||
}
|
||||
msg.pendingResult.result = res;
|
||||
}
|
||||
|
||||
// Save the plugin result so that it can be delivered to the js
|
||||
// even if they miss the initial firing of the event
|
||||
lastResumeEvent = msg;
|
||||
}
|
||||
cordova.fireDocumentEvent(action, msg);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown event action ' + action);
|
||||
}
|
||||
}
|
108
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova-js-src/plugin/android/app.js
vendored
Normal file
108
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova-js-src/plugin/android/app.js
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*
|
||||
*/
|
||||
|
||||
var exec = require('cordova/exec');
|
||||
var APP_PLUGIN_NAME = Number(require('cordova').platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Clear the resource cache.
|
||||
*/
|
||||
clearCache:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "clearCache", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the url into the webview or into new browser instance.
|
||||
*
|
||||
* @param url The URL to load
|
||||
* @param props Properties that can be passed in to the activity:
|
||||
* wait: int => wait msec before loading URL
|
||||
* loadingDialog: "Title,Message" => display a native loading dialog
|
||||
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error
|
||||
* clearHistory: boolean => clear webview history (default=false)
|
||||
* openExternal: boolean => open in a new browser (default=false)
|
||||
*
|
||||
* Example:
|
||||
* navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
|
||||
*/
|
||||
loadUrl:function(url, props) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "loadUrl", [url, props]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel loadUrl that is waiting to be loaded.
|
||||
*/
|
||||
cancelLoadUrl:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "cancelLoadUrl", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear web history in this web view.
|
||||
* Instead of BACK button loading the previous web page, it will exit the app.
|
||||
*/
|
||||
clearHistory:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "clearHistory", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Go to previous page displayed.
|
||||
* This is the same as pressing the backbutton on Android device.
|
||||
*/
|
||||
backHistory:function() {
|
||||
exec(null, null, APP_PLUGIN_NAME, "backHistory", []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android back button.
|
||||
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
|
||||
*
|
||||
* Note: The user should not have to call this method. Instead, when the user
|
||||
* registers for the "backbutton" event, this is automatically done.
|
||||
*
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideBackbutton:function(override) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the default behavior of the Android volume button.
|
||||
* If overridden, when the volume button is pressed, the "volume[up|down]button"
|
||||
* JavaScript event will be fired.
|
||||
*
|
||||
* Note: The user should not have to call this method. Instead, when the user
|
||||
* registers for the "volume[up|down]button" event, this is automatically done.
|
||||
*
|
||||
* @param button volumeup, volumedown
|
||||
* @param override T=override, F=cancel override
|
||||
*/
|
||||
overrideButton:function(button, override) {
|
||||
exec(null, null, APP_PLUGIN_NAME, "overrideButton", [button, override]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Exit and terminate the application.
|
||||
*/
|
||||
exitApp:function() {
|
||||
return exec(null, null, APP_PLUGIN_NAME, "exitApp", []);
|
||||
}
|
||||
};
|
2189
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova.js
vendored
Normal file
2189
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova_plugins.js
vendored
Normal file
9
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/cordova_plugins.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
cordova.define('cordova/plugin_list', function(require, exports, module) {
|
||||
module.exports = [];
|
||||
module.exports.metadata =
|
||||
// TOP OF METADATA
|
||||
{
|
||||
"cordova-plugin-whitelist": "1.3.3"
|
||||
};
|
||||
// BOTTOM OF METADATA
|
||||
});
|
@ -0,0 +1,139 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>asciiblaster</title>
|
||||
<meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=yes" />
|
||||
<link rel="stylesheet" href="assets/css/sally.css" type="text/css" charset="utf-8" />
|
||||
<link rel="stylesheet" href="assets/css/ak.css" type="text/css" charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body class="loading panke">
|
||||
|
||||
<div id="workspace_rapper">
|
||||
<div id="canvas_rapper" class="rapper"></div>
|
||||
</div>
|
||||
|
||||
<div id="ui_rapper">
|
||||
<div class="block" id="tools_block">
|
||||
<div id="palette_rapper"></div>
|
||||
<div id="secret_rapper">
|
||||
<span id="experimental_palette_toggle">.</span>
|
||||
<!-- <span id="nopaint_toggle">N</span> -->
|
||||
</div>
|
||||
<div id="letters_rapper"></div>
|
||||
<div id="custom_rapper"></div>
|
||||
</div>
|
||||
<div id="brush_container" class="block">
|
||||
<div id="brush_rapper">
|
||||
</div>
|
||||
<br>
|
||||
<span id="fg_checkbox" class="tool">x fg</span><br>
|
||||
<span id="bg_checkbox" class="tool">x bg</span><br>
|
||||
<span id="char_checkbox" class="tool">x char</span><br>
|
||||
<br>
|
||||
<span id="add_custom_el" class="tool">+ add</span>
|
||||
<span id="mirror_x_checkbox" class="tool">_ mirror x</span><br>
|
||||
<span id="mirror_y_checkbox" class="tool">_ mirror y</span><br>
|
||||
<br>
|
||||
<span id="undo_el" class="tool hidden">undo</span><br>
|
||||
<span id="redo_el" class="tool hidden">redo</span><br>
|
||||
</div>
|
||||
<div id="tools_rapper" class="block">
|
||||
<span id="square_el" class="tool">square</span><br>
|
||||
<span id="circle_el" class="tool">circle</span><br>
|
||||
<span id="cross_el" class="tool">cross</span><br>
|
||||
<span id="text_el" class="tool">text</span><br>
|
||||
<span id="fill_el" class="tool">fill</span><br>
|
||||
<span id="select_el" class="tool">select</span><br>
|
||||
<br>
|
||||
<span id="rotate_el" class="tool">rotate</span><br>
|
||||
<span id="scale_el" class="tool">scale</span><br>
|
||||
<span id="translate_el" class="tool">translate</span><br>
|
||||
<span id="slice_el" class="tool">slice</span><br>
|
||||
|
||||
<span id="grid_el" class="tool">_ grid</span>
|
||||
<!-- <span id="rotate_checkbox" class="tool">_ rotate</span><br> -->
|
||||
<span id="vertical_checkbox" class="tool">x vertical</span>
|
||||
<!-- <span id="pixels_checkbox" class="tool">_ pixels</span><br> -->
|
||||
</div>
|
||||
|
||||
<div id="textarea_mode" style="float: left">
|
||||
<div>
|
||||
<span id="clear_el" class="tool">new</span>
|
||||
<span id="save_el" class="tool">save</span>
|
||||
<span id="load_el" class="tool">load</span>
|
||||
<br>
|
||||
<span id="shader_el" class="tool">shader</span>
|
||||
<a id="doc_el" href="assets/doc/index.html" target="_blank">doc</a>
|
||||
<br>
|
||||
<span id="advanced_checkbox" class="tool">_ advanced</span>
|
||||
<br>
|
||||
<br>
|
||||
<div id="nopaint_rapper">
|
||||
<br>
|
||||
<span id="nopaint_no_el" class="tool">no</span><br>
|
||||
<span id="nopaint_paint_el" class="tool">paint</span><br>
|
||||
<span id="nopaint_pause_el" class="tool hidden">pause</span><br>
|
||||
</div>
|
||||
<br>
|
||||
brush: <span id="brush_w_el" class="ed">5</span> x <span id="brush_h_el" class="ed">5</span><br>
|
||||
canvas: <span id="canvas_w_el" class="ed">100</span> x <span id="canvas_h_el" class="ed">30</span><br>
|
||||
</div>
|
||||
|
||||
<div id="import_rapper">
|
||||
<span id="format_el">ascii *irssi mirc ansi</span>
|
||||
<span id="import_buttons">
|
||||
<button id="import_button">import</button>
|
||||
</span>
|
||||
<div id="gallery_rapper" /><br >
|
||||
<div id="cutoff_warning_el">character limit of 425 exceeded</div>
|
||||
<textarea id="import_textarea"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="shader_rapper">
|
||||
<span id="animate_checkbox" class="tool">_ animate</span>
|
||||
to <span id="shader_target_el">*canvas brush selection</span>
|
||||
<span id="shader_fps_el" class="hidden faded"></span><br>
|
||||
<textarea id="shader_textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="text" id="cursor_input">
|
||||
|
||||
</body>
|
||||
<script type="text/javascript-shader" id="demo_shader">
|
||||
// lex.bg = hue((x+y*y+t/10)/20)
|
||||
// lex.fg = colors.white
|
||||
// lex.char = " "
|
||||
// lex.opacity = 1
|
||||
|
||||
</script>
|
||||
<script src="assets/js/ext/oktween.js"></script>
|
||||
|
||||
<script src="assets/js/ext/util.js"></script>
|
||||
<script src="assets/js/ext/unicode.js"></script>
|
||||
<script src="assets/js/ext/color.js"></script>
|
||||
<script src="assets/js/clipboard.js"></script>
|
||||
|
||||
<script src="assets/js/lex.js"></script>
|
||||
<script src="assets/js/matrix.js"></script>
|
||||
<script src="assets/js/gfx.js"></script>
|
||||
<script src="assets/js/ui/tool.js"></script>
|
||||
<script src="assets/js/gfx.js"></script>
|
||||
|
||||
<script src="assets/js/ui/brush.js"></script>
|
||||
<script src="assets/js/ui/canvas.js"></script>
|
||||
<script src="assets/js/ui/custom.js"></script>
|
||||
<script src="assets/js/ui/keys.js"></script>
|
||||
<script src="assets/js/ui/controls.js"></script>
|
||||
<script src="assets/js/ui/palette.js"></script>
|
||||
<script src="assets/js/ui/letters.js"></script>
|
||||
<script src="assets/js/ui/selection.js"></script>
|
||||
<script src="assets/js/ui/transform.js"></script>
|
||||
<script src="assets/js/ext/nopaint.js"></script>
|
||||
|
||||
<script src="assets/js/app.js"></script>
|
||||
</html>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user