Compare commits

...

10 commits

9 changed files with 774 additions and 143 deletions

View file

@ -1,7 +1,6 @@
# Android App (build via docker/podman)
# Calender App
The goal of this repo, is to create a container that can serve to produce an "empty android Application" (i.e `app.apk` file).
As such the philosophy is to keep the process "simple" as to now make the understanding too difficult.
simple calender app
## usage
@ -12,82 +11,7 @@ As such the philosophy is to keep the process "simple" as to now make the unders
./build-android-app.sh
```
2. follow the configuration
3. upon success the apk file created is in app/result/app.apk and can be installed via `adb`
```
adb install -r app/result/app.apk
```
## basic ideas
## Screenshots
* work within container (debian based image)
* use Makefile as a build tool
## benefits of this (compared to AndroidStudio)
* no need to install rather bloated hell of software (i.e AndroidStudio) and all
* less hidding of internals (i.e Makefile allows to see how app.apk is made)
* small app.apk file
* oftentimes faster compile time (as compared with AndroidStudio Gradle builds)
* quick "webview" which can serve as starting point for people that can to PWA and websites
* Assisted initial configuration provides access to configure almost all types of [Android app permissions](app/.Makefile.scripts/make--app-config.sh#L127)
* no need to have Kotlin, Gradle setup
/
## basic info
This repo should allow to generate an empty "android app". by simply cloning this repo and
```
./build-android-app.sh
```
It does so via:
1. building a container (in any of the runtime/daemons it finds: i.e. docker,podman,etc..)
2. running this container having the `./app` folder being mounted within as `/app`
3. executing the [`app/Makefile`](app/Makefile) which will then:
4. either work with the configuration stored in an `app/app-config.sh` in case such file exists or
5. if not go through a `whiptail` text menu wizzard to configure a new empty app. (Makefile recipe: `./app-config.sh`)
6. it will then download the required android sdk files as necessary (Makefile recipe: `./android-sdk/installed`)
7. go through the further steps to setup the blank app.
## files and purpose
Upon `clone` of this repo the `app` folder is setup with these files:
```
# The (GNU) Makefile which...
app/Makefile
# ... has recipes that call scripts in folder....
app/.Makefile.scripts
# .. which creates an `app-config.sh`, a file to keep
# the configuration (app name,lable,api-levels,permissions etc)...
app/.Makefile.scripts/make--app-config.sh
# .. which creates an `AndroidManifest.xml`
app/.Makefile.scripts/make--AndroidManifest.xml
# .. which creates an `AppActivity.java` file (which just setup a Webview and loads `assets/index.html`)
app/.Makefile.scripts/make--AppActivity.java.sh
# .. which installs the necessary Android SDK in the correct versions
app/.Makefile.scripts/make--android-sdk.sh
app/assets
# the index.html file
app/assets/index.html
app/res
app/res/drawable
# the icon of the app
app/res/drawable/appicon.xml
```
Upon further `./build-android-app.sh` execution more folders will appear
```
# a folder in which the Android-sdk stuff (installed via sdkmanager) is stored
android-sdk
# folders used during build...
# ... for temporary files
bin/
obj/
result/
# app configuration resulting from text whiptail menu
app-config.sh
# the Manifest file as resulted from data from app-config.sh
AndroidManifest.xml
```
![screenshot of calender app](screenshots/screenshot.01.avif)

View file

@ -8,31 +8,21 @@ test -f app-config.sh && {
echo "package $APP_PACKAGE;"
cat << 'APPACTIVITYJAVA'
import android.provider.Settings ;
import android.content.Intent;
import android.util.Log;
import android.util.Base64;
import java.util.Objects;
import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Environment;
import android.text.method.ScrollingMovementMethod;
import android.view.*;
//import android.view.MenuItem;
import android.view.ViewGroup.*;
import android.widget.*;
//import android.widget.Toast;
//import android.widget.TextView;
import android.view.*;
// for WebView,WebMessage,WebMessagePort,
import android.webkit.*;
import android.net.Uri;
import org.json.JSONObject;
//import android.webkit.WebView;
//import android.webkit.WebMessage;
//import android.webkit.WebMessagePort;
import java.io.InputStream;
import java.io.File;
import java.util.Objects;
@ -70,6 +60,9 @@ public class AppActivity extends Activity {
myWebSettings.setBuiltInZoomControls(true);
myWebSettings.setDisplayZoomControls(false);
myWebSettings.setJavaScriptEnabled(true);
myWebSettings.setDomStorageEnabled(true);
myWebSettings.setDatabaseEnabled(true);
myWebSettings.setDatabasePath("/data/data/" + myWebView.getContext().getPackageName() + "/databases/");
myWebView.addJavascriptInterface(this, "myJavaScriptInterface");
// load the html from assets file
String html = readFileFromAssets("index.html");
@ -85,4 +78,3 @@ public class AppActivity extends Activity {
return "this is good";
}
}
APPACTIVITYJAVA

View file

@ -47,23 +47,28 @@ include Makefile.app-config
# make the resources "R.java" thing
./src/PACKAGE/R.java : $(shell find ./res -type f) app-config.sh ./AndroidManifest.xml ./android-sdk/installed
./src/$(PACKAGE)/R.java : $(shell find ./res -type f) app-config.sh ./AndroidManifest.xml ./android-sdk/installed | ./src/$(PACKAGE)
$(BUILDTOOLS)/aapt package \
-v -f -m -S ./res -J ./src -M ./AndroidManifest.xml \
-I $(ANDROID_JAR)
# generate the AppActivity.java (template)
./src/package/AppActivity.java: app-config.sh
# generate the AppActivity.java (template
# the "|" denotes an "order-only" prerequiste (as in https://stackoverflow.com/a/58040049/1711186)
./src/$(PACKAGE)/AppActivity.java: app-config.sh | ./src/$(PACKAGE)
./.Makefile.scripts/make--AppActivity.java.sh > $@
./src/$(PACKAGE):
mkdir -p $@
# install the necessary android sdks
./android-sdk/installed: app-config.sh
./android-sdk/installed: app-config.sh ./.Makefile.scripts/make--android-sdk.sh
./.Makefile.scripts/make--android-sdk.sh
# generate the AndroidManifest.xml
./AndroidManifest.xml: app-config.sh
./AndroidManifest.xml: app-config.sh ./.Makefile.scripts/make--AndroidManifest.xml
./.Makefile.scripts/make--AndroidManifest.xml
# !!this step (when/if) run will trigger a restart of the "make" as the rules tartget is included
Makefile.app-config: app-config.sh Makefile
source app-config.sh; \
tee $@ << MAKEFILE_APP_CONFIG

View file

@ -4,50 +4,286 @@
<meta charset="UTF-8">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
</head>
<style>
@keyframes wobble {
0% {
transform: scale(0.1);
/* transform: rotate(-5deg);
transform: rotate(5deg);*/
opacity:0.0
}
22% {
transform: scale(2.0);
/*/ transform: rotate(-5deg);*/
opacity:1.0
}
99% {
transform: scale(1.0);
/*/ transform: rotate(-5deg);*/
opacity:0.0
}
<style>
* {
font-family: sans;
box-sizing:border-box;
}
body,html{
margin:0;
padding:0
}
.infinityScroll{
height:50vh;
overflow:scroll;
border:1px solid red;
position:relative;
}
.space{
background:linear-gradient(white, blue);
height:300%;
opacity:0.5;
}
</style>
<body style=" background: radial-gradient(#111, #000);">
<h1> SUPER</h1>
<script>
var count=0;
window.addEventListener("load",()=>{
function insert(){
count++;
var h2 = document.createElement("h2");
h2.textContent=" "+ count + ". javascript works"
h2.style.color="#"+Math.random().toString(16).slice(3,9);
// h2.style.transform="rotate("+((Math.random()*3600)|0)/10+"deg)";
h2.style.fontSize=""+Math.random()*20+"pt";
h2.style.textAlign="center";
h2.style.animation="wobble 5s ease-in-out infinite alternate"
document.body.insertBefore(h2,document.body.children[(document.body.children.length*Math.random())|0])
// h2.scrollIntoView({"behavior":"smooth"});
if(document.body.children.length>15){
document.body.children[(document.body.children.length*Math.random())|0].remove();
<style>
* {
font-family: sans;
}
:root {
touch-action: pan-x pan-y;
height: 100%
}
body{
background-color:#ffa;
margin:0
}
.monthview {
background-color:rgba(255,255,255,0.5);
border:2px solid rgba(0,0,0,0.5);
border-radius: 10%;
margin:5px;
min-height: 20vw;
/*margin:5px;*/
/* scroll-snap-align: center;*/
overflow:hidden;
}
.label{
background-color:#a44;
color:#fff;
font-size:10vw;
text-align:center;
}
.days{
display: grid;
width: 100%;
grid-template-columns: repeat(7, 1fr);
}
.days div {
text-align:center;
height: 2cm;
align:content;
border:1px solid grey;
}
#mainDiv{
box-sizing:border-box;
margin:0;
/* scroll-snap-type: y mandatory;*/
height:100vh;
overflow:scroll;
border: 1px solid red;
margin:0;
width:100vw;
}
.orig{
border: 3px solid blue;
}
.weekend{
color:red;
background-color:white;
}
</style>
<body>
<div id="mainDiv" class="infinityScroll"></div>
<script>
// goals:
// 1. no/huge limits
// 1.1. scroll basis is a number (and hence scroll can continues as long as number can increase/descrease)
// 2. both directions
// 3. resetability
// answers
// todo: at center of scroll will be a div containing the number
// todo: onnce scrolling settled, the position reached is the new center
// questions
// Q1: what is to be scrolled ?
// A1: elements that are set to `.infinityScroll` class
// Q2: how is the scroll to infinity implemented (exponential stuff)
window.addEventListener("load",()=>{
var locale = window.navigator.userLanguage || window.navigator.language || "en";
function createMonthView(date = new Date()){
var monthviewDiv = document.createElement("div")
monthviewDiv.className = "monthview";
var labelDiv = document.createElement("div");
labelDiv.textContent = date.toLocaleString(locale,{"month":"long","year":"numeric"})
labelDiv.className = "label"
monthviewDiv.appendChild(labelDiv);
var daysDiv = document.createElement("div");
daysDiv.className="days";
monthviewDiv.appendChild(daysDiv);
// mainDiv.appendChild(monthviewDiv);
monthviewDiv.scrollIntoView();
// var weekendDiv = document.createElement("div");
// weekendDiv.className = "weekend";
// daysDiv.appendChild(weekendDiv);
var day = new Date(date);
day.setDate(1);
while(day.getMonth() == date.getMonth()){
var dayDiv = document.createElement("div");
dayDiv.className = "day";
var column = day.getDay();
column = column == 0 ? 7: column;
if(column>=6){
dayDiv.classList.add("weekend");
}
dayDiv.style.gridColumn = column;
dayDiv.textContent = day.getDate()
daysDiv.appendChild(dayDiv);
day.setDate(day.getDate()+1);
}
return monthviewDiv;
}
function makeInfinityScroll(element){
element.nn=element.nn||{"position":0};
var mdate;
//var before = document.createElement("div");
//var after = document.createElement("div");
//after.style.backgroundColor="blue";
//var center = document.createElement("div");
//before.className = after.className = "space";
//after.style.background="linear-gradient(yellow,red)";
//center.textContent="center "+element.nn.position;
//center.style.border="1px solid red";
//center = createMonthView();
//center.nnindex=0;
//element.appendChild(after);
for(var i = -3; i< 3; i++){
mdate = new Date();
mdate.setMonth(mdate.getMonth()+i);
var nextMonth=createMonthView(mdate);
nextMonth.nnindex=i;
element.appendChild(nextMonth);
nextMonth.classList.add('orig');
}
element.children[3].scrollIntoView({block:"center"});
setTimeout(()=>{
//console.log(element.firstChild,element.firstChild.nnindex);
var beforeind=0;
console.log("fc",element,element.firstChild);
if(element.firstChild.nnindex){
beforeind = element.firstChild.nnindex
}
mdate = new Date();
mdate.setMonth(mdate.getMonth()+beforeind-1);
var newBefore = createMonthView(mdate);
newBefore.nnindex=beforeind-1;
// newBefore.className="space";
console.log("prio "+element.scrollTop,""+element.scrollHeight,element.clientHeight);
// var savscrolltop = element.scrollTop;
element.insertBefore(newBefore,element.firstChild);
// element.scrollTop=savscrolltop-newBefore.clientHeight;
// var beforeind=0;
// console.log("fc",element,element.firstChild);
// if(element.firstChild.nnindex){
// beforeind = element.firstChild.nnindex
// }
// mdate = new Date();
// mdate.setMonth(mdate.getMonth()+beforeind-1);
// var newBefore = createMonthView(mdate);
// newBefore.nnindex=beforeind-1;
//// newBefore.className="space";
// console.log("prio "+element.scrollTop,""+element.scrollHeight,element.clientHeight);
// var savscrolltop = element.scrollTop;
// element.insertBefore(newBefore,element.firstChild);
// element.scrollTop=savscrolltop-newBefore.clientHeight;
//
// console.log("POST "+element.scrollTop,""+element.scrollHeight,element.clientHeight);
//
},100000);
element.addEventListener("scroll",()=>{
if(element.scrollUpdateSchedule!=1){
element.scrollUpdateSchedule=1;
requestAnimationFrame(()=>{
element.scrollUpdateSchedule=0;
//if(element.scrollTop/element.scrollHeight < 0.2){
// console.log("ch",element.clientHeight,element.scrollTop);
if(element.scrollTop < 3* element.clientHeight)
{
console.log(element.scrollTop)
//console.log(element.firstChild,element.firstChild.nnindex);
var beforeind=0;
if(element.firstChild.nnindex){
beforeind = element.firstChild.nnindex
}
mdate = new Date();
mdate.setMonth(mdate.getMonth()+beforeind-1);
var newBefore = createMonthView(mdate);
newBefore.nnindex=beforeind-1;
// newBefore.className="space";
console.log("prio"+element.scrollTop,element.scrollHeight);
element.insertBefore(newBefore,element.firstChild);
console.log("POST"+element.scrollTop,element.scrollHeight);
// while(element.scrollHeight>200*element.clientHeight){
// element.children[element.children.length-1].remove();
// console.log("REMOVED"+element.scrollTop);
// }
}
setTimeout(()=>{window.requestAnimationFrame(insert);},100);
}
insert();
},false);
</script>
</body>
});
}
},false);
}
/////-- if(element.scrollTop > element.scrollHeight - 4 * element.clientHeight)
/////-- {
/////-- //var afterind = element.children[element.children.length].nnindex || 0;
/////-- var afterind=0;
/////-- if(element.children[element.children.length-1].nnindex){
/////-- afterind = element.children[element.children.length-1].nnindex;
/////-- }
/////-- var newAfter = document.createElement("div");
/////--// newBefore.className="space";
/////-- newAfter.innerHTML=new Array(200).fill("a").map((a,i)=>{return afterind+"-"+i;}).join("<br>");
/////-- newAfter.nnindex=afterind+1;
/////-- console.log("prio"+element.scrollTop);
/////-- element.appendChild(newAfter);
/////-- console.log("POST"+element.scrollTop);
/////--
/////--
/////--// while(element.scrollHeight>200*element.clientHeight){
/////--// element.children[0].remove();
/////--// console.log("REMOVED"+element.scrollTop);
/////--// }
/////--
/////--
/////-- }
/////--// console.log(Date.now(),"Udpate",element.scrollTop);
/////-- console.log(Date.now(),"Udpate",element.scrollTop/element.scrollHeight,"AAA",element.scrollTop,">",element.scrollHeight - 4 * element.clientHeight,element.scrollHeight);
/////-- element.scrollUpdateSchedule=0;
/////-- });
/////-- }
// console.log(element.scrollTop);
// },false);
// }
document.querySelectorAll('.infinityScroll').forEach(makeInfinityScroll);
},false);
window.addEventListener("error",(error)=>{ document.body.innerHTML = "<h1>error</h1><pre>" + error.filename + "\nline:" + error.lineno + "\n"+error.message +"</pre>"; },false);
</script>
</body>
</html>

182
app/assets/index.html.1 Normal file
View file

@ -0,0 +1,182 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
</head>
<style>
:root {
touch-action: pan-x pan-y;
height: 100%
}
@keyframes wobble {
0% {
transform: scale(0.1);
opacity:0.0
}
100% {
transform: scale(1.0);
opacity:1.0
}
}
body{
background-color:#ffa;
}
div {
margin: 0em;
padding: 2em;
}
#target {
background: white;
border: 1px solid black;
}
</style>
<!--<body onload="init();" style="touch-action:none">-->
<body onload="init();">
<div id="target">
Touch and Hold with 2 pointers, then pinch in or out.<br />
The background color will change to pink if the pinch is opening (Zoom In)
or changes to lightblue if the pinch is closing (Zoom out).
</div>
<!-- UI for logging/debugging -->
<button id="log" onclick="enableLog(event);">Start/Stop event logging</button>
<button id="clearlog" onclick="clearLog(event);">Clear the log</button>
<p></p>
<output></output>
<a href="https://server.alexmahr.de/calender">calender</a>
<h1>Webview</h1>
<pre id="output">output</pre>
<script>
window.addEventListener("error",(error)=>{
document.body.innerHTML = "<h1>error</h1><pre>" +
error.filename +
"\nline:" + error.lineno +
"\n"+error.message +"</pre>";
},false);
['touchstart','touchmove','touchend'].forEach((evname)=>{
window.addEventListener(evname,(ev)=>{
output.textContent+=evname+"\n" ;
ev.preventDefault();
},false);
});
window.addEventListener("load",()=>{
var count = localStorage.getItem("app-opened-count")|| 0;
count++;
localStorage.setItem("app-opened-count",count);
var h2 = document.createElement("h2");
h2.textContent = "Javascript works! (app was opened " + count + " times)";
h2.style.animation="wobble 1s ease-in-out 0s 1 forwards normal running"
document.body.appendChild(h2);
},false);
// Global vars to cache event state
const evCache = [];
let prevDiff = -1;
function init() {
// Install event handlers for the pointer target
const el = document.getElementById("target");
el.onpointerdown = pointerdownHandler;
el.onpointermove = pointermoveHandler;
// Use same handler for pointer{up,cancel,out,leave} events since
// the semantics for these events - in this app - are the same.
el.onpointerup = pointerupHandler;
el.onpointercancel = pointerupHandler;
el.onpointerout = pointerupHandler;
el.onpointerleave = pointerupHandler;
}
function pointerdownHandler(ev) {
// The pointerdown event signals the start of a touch interaction.
// This event is cached to support 2-finger gestures
evCache.push(ev);
log("pointerDown", ev);
}
function pointermoveHandler(ev) {
// This function implements a 2-pointer horizontal pinch/zoom gesture.
//
// If the distance between the two pointers has increased (zoom in),
// the target element's background is changed to "pink" and if the
// distance is decreasing (zoom out), the color is changed to "lightblue".
//
// This function sets the target element's border to "dashed" to visually
// indicate the pointer's target received a move event.
log("pointerMove", ev);
ev.target.style.border = "dashed";
// Find this event in the cache and update its record with this event
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache[index] = ev;
// If two pointers are down, check for pinch gestures
if (evCache.length === 2) {
// Calculate the distance between the two pointers
const curDiff = Math.abs(evCache[0].clientX - evCache[1].clientX);
if (prevDiff > 0) {
if (curDiff > prevDiff) {
// The distance between the two pointers has increased
log("Pinch moving OUT -> Zoom in", ev);
ev.target.style.background = "pink";
}
if (curDiff < prevDiff) {
// The distance between the two pointers has decreased
log("Pinch moving IN -> Zoom out", ev);
ev.target.style.background = "lightblue";
}
}
// Cache the distance for the next move event
prevDiff = curDiff;
}
}
function pointerupHandler(ev) {
log(ev.type, ev);
// Remove this pointer from the cache and reset the target's
// background and border
removeEvent(ev);
ev.target.style.background = "white";
ev.target.style.border = "1px solid black";
// If the number of pointers down is less than two then reset diff tracker
if (evCache.length < 2) {
prevDiff = -1;
}
}
function removeEvent(ev) {
// Remove this event from the target's cache
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache.splice(index, 1);
}
// Log events flag
let logEvents = false;
// Logging/debugging functions
function enableLog(ev) {
logEvents = !logEvents;
}
function log(prefix, ev) {
if (!logEvents) return;
const o = document.getElementsByTagName("output")[0];
o.innerText += `${prefix}:
pointerID = ${ev.pointerId}
pointerType = ${ev.pointerType}
isPrimary = ${ev.isPrimary}
`;
}
function clearLog(event) {
const o = document.getElementsByTagName("output")[0];
o.textContent = "";
}
</script>
</body>
</html>

View file

@ -0,0 +1,182 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
</head>
<style>
:root {
touch-action: pan-x pan-y;
height: 100%
}
@keyframes wobble {
0% {
transform: scale(0.1);
opacity:0.0
}
100% {
transform: scale(1.0);
opacity:1.0
}
}
body{
background-color:#ffa;
}
div {
margin: 0em;
padding: 2em;
}
#target {
background: white;
border: 1px solid black;
}
</style>
<!--<body onload="init();" style="touch-action:none">-->
<body onload="init();">
<div id="target">
Touch and Hold with 2 pointers, then pinch in or out.<br />
The background color will change to pink if the pinch is opening (Zoom In)
or changes to lightblue if the pinch is closing (Zoom out).
</div>
<!-- UI for logging/debugging -->
<button id="log" onclick="enableLog(event);">Start/Stop event logging</button>
<button id="clearlog" onclick="clearLog(event);">Clear the log</button>
<p></p>
<output></output>
<a href="https://server.alexmahr.de/calender">calender</a>
<h1>Webview</h1>
<pre id="output">output</pre>
<script>
window.addEventListener("error",(error)=>{
document.body.innerHTML = "<h1>error</h1><pre>" +
error.filename +
"\nline:" + error.lineno +
"\n"+error.message +"</pre>";
},false);
['touchstart','touchmove','touchend'].forEach((evname)=>{
window.addEventListener(evname,(ev)=>{
output.textContent+=evname+"\n" ;
ev.preventDefault();
},false);
});
window.addEventListener("load",()=>{
var count = localStorage.getItem("app-opened-count")|| 0;
count++;
localStorage.setItem("app-opened-count",count);
var h2 = document.createElement("h2");
h2.textContent = "Javascript works! (app was opened " + count + " times)";
h2.style.animation="wobble 1s ease-in-out 0s 1 forwards normal running"
document.body.appendChild(h2);
},false);
// Global vars to cache event state
const evCache = [];
let prevDiff = -1;
function init() {
// Install event handlers for the pointer target
const el = document.getElementById("target");
el.onpointerdown = pointerdownHandler;
el.onpointermove = pointermoveHandler;
// Use same handler for pointer{up,cancel,out,leave} events since
// the semantics for these events - in this app - are the same.
el.onpointerup = pointerupHandler;
el.onpointercancel = pointerupHandler;
el.onpointerout = pointerupHandler;
el.onpointerleave = pointerupHandler;
}
function pointerdownHandler(ev) {
// The pointerdown event signals the start of a touch interaction.
// This event is cached to support 2-finger gestures
evCache.push(ev);
log("pointerDown", ev);
}
function pointermoveHandler(ev) {
// This function implements a 2-pointer horizontal pinch/zoom gesture.
//
// If the distance between the two pointers has increased (zoom in),
// the target element's background is changed to "pink" and if the
// distance is decreasing (zoom out), the color is changed to "lightblue".
//
// This function sets the target element's border to "dashed" to visually
// indicate the pointer's target received a move event.
log("pointerMove", ev);
ev.target.style.border = "dashed";
// Find this event in the cache and update its record with this event
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache[index] = ev;
// If two pointers are down, check for pinch gestures
if (evCache.length === 2) {
// Calculate the distance between the two pointers
const curDiff = Math.abs(evCache[0].clientX - evCache[1].clientX);
if (prevDiff > 0) {
if (curDiff > prevDiff) {
// The distance between the two pointers has increased
log("Pinch moving OUT -> Zoom in", ev);
ev.target.style.background = "pink";
}
if (curDiff < prevDiff) {
// The distance between the two pointers has decreased
log("Pinch moving IN -> Zoom out", ev);
ev.target.style.background = "lightblue";
}
}
// Cache the distance for the next move event
prevDiff = curDiff;
}
}
function pointerupHandler(ev) {
log(ev.type, ev);
// Remove this pointer from the cache and reset the target's
// background and border
removeEvent(ev);
ev.target.style.background = "white";
ev.target.style.border = "1px solid black";
// If the number of pointers down is less than two then reset diff tracker
if (evCache.length < 2) {
prevDiff = -1;
}
}
function removeEvent(ev) {
// Remove this event from the target's cache
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache.splice(index, 1);
}
// Log events flag
let logEvents = false;
// Logging/debugging functions
function enableLog(ev) {
logEvents = !logEvents;
}
function log(prefix, ev) {
if (!logEvents) return;
const o = document.getElementsByTagName("output")[0];
o.innerText += `${prefix}:
pointerID = ${ev.pointerId}
pointerType = ${ev.pointerType}
isPrimary = ${ev.isPrimary}
`;
}
function clearLog(event) {
const o = document.getElementsByTagName("output")[0];
o.textContent = "";
}
</script>
</body>
</html>

105
app/assets/pinch.js Normal file
View file

@ -0,0 +1,105 @@
// Global vars to cache event state
const evCache = [];
let prevDiff = -1;
function init() {
// Install event handlers for the pointer target
const el = document.getElementById("target");
el.onpointerdown = pointerdownHandler;
el.onpointermove = pointermoveHandler;
// Use same handler for pointer{up,cancel,out,leave} events since
// the semantics for these events - in this app - are the same.
el.onpointerup = pointerupHandler;
el.onpointercancel = pointerupHandler;
el.onpointerout = pointerupHandler;
el.onpointerleave = pointerupHandler;
}
function pointerdownHandler(ev) {
// The pointerdown event signals the start of a touch interaction.
// This event is cached to support 2-finger gestures
evCache.push(ev);
log("pointerDown", ev);
}
function pointermoveHandler(ev) {
// This function implements a 2-pointer horizontal pinch/zoom gesture.
//
// If the distance between the two pointers has increased (zoom in),
// the target element's background is changed to "pink" and if the
// distance is decreasing (zoom out), the color is changed to "lightblue".
//
// This function sets the target element's border to "dashed" to visually
// indicate the pointer's target received a move event.
log("pointerMove", ev);
ev.target.style.border = "dashed";
// Find this event in the cache and update its record with this event
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache[index] = ev;
// If two pointers are down, check for pinch gestures
if (evCache.length === 2) {
// Calculate the distance between the two pointers
const curDiff = Math.abs(evCache[0].clientX - evCache[1].clientX);
if (prevDiff > 0) {
if (curDiff > prevDiff) {
// The distance between the two pointers has increased
log("Pinch moving OUT -> Zoom in", ev);
ev.target.style.background = "pink";
}
if (curDiff < prevDiff) {
// The distance between the two pointers has decreased
log("Pinch moving IN -> Zoom out", ev);
ev.target.style.background = "lightblue";
}
}
// Cache the distance for the next move event
prevDiff = curDiff;
}
}
function pointerupHandler(ev) {
log(ev.type, ev);
// Remove this pointer from the cache and reset the target's
// background and border
removeEvent(ev);
ev.target.style.background = "white";
ev.target.style.border = "1px solid black";
// If the number of pointers down is less than two then reset diff tracker
if (evCache.length < 2) {
prevDiff = -1;
}
}
function removeEvent(ev) {
// Remove this event from the target's cache
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache.splice(index, 1);
}
// Log events flag
let logEvents = false;
// Logging/debugging functions
function enableLog(ev) {
logEvents = !logEvents;
}
function log(prefix, ev) {
if (!logEvents) return;
const o = document.getElementsByTagName("output")[0];
o.innerText += `${prefix}:
pointerID = ${ev.pointerId}
pointerType = ${ev.pointerType}
isPrimary = ${ev.isPrimary}
`;
}
function clearLog(event) {
const o = document.getElementsByTagName("output")[0];
o.textContent = "";
}

View file

@ -36,7 +36,11 @@ RUN <<EOF
cat > /bin/makefile-bash-wrapper.sh << 'WRAPPER'
#!/bin/bash
printf $'\033[0;32m''#----------------------------------------\n'$'\033[0m' >&2
bash "$@"
bash -e "$@" || {
EXITCODE=$?
printf $'\033[0;31m''ERROR EXITCODE='"$EXITCODE"'\n'$'\033[0m' >&2
exit $EXITCODE
}
printf '\n\n\n\n' >&2
WRAPPER
chmod u+x /bin/makefile-bash-wrapper.sh
@ -44,6 +48,7 @@ EOF
DOCKERFILEEOF
}
printf $'\033[0;33m'"$(date -Iseconds) starting build"'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n'$'\033[0m'
diff Dockerfile <(DockerfileContent) 2>/dev/null > /dev/null || {
test -f Dockerfile && {
read -p 'reset/start Dockerfile[Y/n]' YES

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB