Initial work on unofficial amazfit format compatible parser

master
Martin Boonk 2022-02-24 23:15:58 +01:00
parent 51a5b42f0c
commit 7a62248f6b
4 changed files with 730 additions and 79 deletions

View File

@ -1,2 +1,3 @@
0.01: New App
0.02: Allow drawing polys
0.03: Allow partly importing Amazfit decompiler formatted watchfaces

View File

@ -30,16 +30,23 @@ function prepareImg(resource){
}
function getByPath(object, path, lastElem){
//print("getByPath", path,lastElem);
var current = object;
for (var c of path){
if (!current[c]) return undefined;
current = current[c];
if (path.length) {
for (var c of path){
if (!current[c]) return undefined;
current = current[c];
}
}
if (lastElem!==undefined){
if (!current["" + lastElem]) return undefined;
//print("Found by lastElem", lastElem);
current = current["" + lastElem];
}
if (typeof current == "function") return undefined;
if (typeof current == "function"){
//print("current was function");
return undefined;
}
return current;
}
@ -49,6 +56,14 @@ function splitNumberToDigits(num){
function drawNumber(element, offset){
var number = numbers[element.Value]();
var spacing = element.Spacing ? element.Spacing : 0;
var unit = element.Unit;
var imageIndexMinus = element.ImageIndexMinus;
var imageIndexUnit = element.ImageIndexUnit;
var numberOfDigits = element.Digits;
//print("drawNumber: ", number, element, offset);
if (number) number = number.toFixed(0);
@ -59,8 +74,9 @@ function drawNumber(element, offset){
var isNegative;
var digits;
if (number == undefined){
isNegative = false;
digits = ["minus","minus","minus"];
isNegative = true;
digits = [];
numberOfDigits = 0;
} else {
isNegative = number < 0;
if (isNegative) number *= -1;
@ -68,27 +84,69 @@ function drawNumber(element, offset){
}
//print("digits: ", digits);
var numberOfDigits = element.Digits;
if (!numberOfDigits) numberOfDigits = digits.length;
var firstDigitX = element.X;
var firstDigitY = element.Y;
var firstImage = getByPath(resources, element.ImagePath, 0);
var imageIndex = element.ImageIndex ? element.ImageIndex : 0;
if (element.Alignment == "BottomRight"){
var digitWidth = firstImage.width + element.Spacing;
var numberWidth = (numberOfDigits * digitWidth);
if (isNegative){
numberWidth += firstImage.width + element.Spacing;
}
//print("Number width: ", numberWidth, firstImage.width, element.Spacing);
var firstImage;
if (imageIndex){
firstImage = getByPath(resources, [], "" + (0 + imageIndex));
} else {
firstImage = getByPath(resources, element.ImagePath, 0);
}
var minusImage;
if (imageIndexMinus){
minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus));
} else {
minusImage = getByPath(resources, element.ImagePath, "minus");
}
var unitImage;
//print("Get image for unit", imageIndexUnit);
if (imageIndexUnit !== undefined){
unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit));
//print("Unit image is", unitImage);
} else if (element.Unit){
unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown"));
}
var numberWidth = (numberOfDigits * firstImage.width) + (Math.max((numberOfDigits - 1),0) * spacing);
if (isNegative && minusImage){
//print("Adding to width", minusImage);
numberWidth += minusImage.width + spacing;
}
if (unitImage){
//print("Adding to width", unitImage);
numberWidth += unitImage.width + spacing;
}
//print("numberWidth:", numberWidth);
if (element.Alignment == "Center") {
firstDigitX = Math.round(element.X - (numberWidth/2)) + 1;
firstDigitY = Math.round(element.Y - (firstImage.height/2)) + 1;
} else if (element.Alignment == "BottomRight"){
firstDigitX = element.X - numberWidth + 1;
firstDigitY = element.Y - firstImage.height + 1;
//print("Calculated start " + firstDigitX + "," + firstDigitY + " From:" + element.BottomRightX + " " + firstImage.width + " " + element.Spacing);
} else if (element.Alignment == "TopRight") {
firstDigitX = element.X - numberWidth + 1;
firstDigitY = element.Y;
} else if (element.Alignment == "BottomLeft") {
firstDigitX = element.X;
firstDigitY = element.Y - firstImage.height + 1;
}
var currentX = firstDigitX;
if (isNegative){
if (isNegative && minusImage){
//print("Draw minus at", currentX);
if (imageIndexMinus){
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "" + (0 + imageIndexMinus));
} else {
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "minus");
}
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "minus");
currentX += firstImage.width + element.Spacing;
currentX += minusImage.width + spacing;
}
for (var d = 0; d < numberOfDigits; d++){
var currentDigit;
@ -99,8 +157,14 @@ function drawNumber(element, offset){
currentDigit = 0;
}
//print("Digit " + currentDigit + " " + currentX);
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, currentDigit);
currentX += firstImage.width + element.Spacing;
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, currentDigit + imageIndex);
currentX += firstImage.width + spacing;
}
if (imageIndexUnit){
//print("Draw unit at", currentX);
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, "" + (0 + imageIndexUnit));
} else if (element.Unit){
drawElement({X:currentX,Y:firstDigitY}, numberOffset, element.ImagePath, getMultistate(element.Unit,"unknown"));
}
element.lastDrawnValue = number;
}
@ -113,13 +177,15 @@ function setColors(properties){
function drawElement(pos, offset, path, lastElem){
//print("drawElement ",pos, offset, path, lastElem);
//print("drawElement offset", offset, pos.X, pos.Y);
var resource = getByPath(resources, path, lastElem);
//print("Got resource", resource);
if (resource){
//print("resource ",pos, offset, path, lastElem);
//print("resource ", resource,pos, offset, path, lastElem);
var image = prepareImg(resource);
if (image){
offset = updateColors(pos, offset);
setColors(offset);
var imageOffset = updateColors(pos, offset);
setColors(imageOffset);
//print("drawImage from drawElement", image, pos, offset);
var options={};
if (pos.RotationValue){
@ -131,7 +197,7 @@ function drawElement(pos, offset, path, lastElem){
}
//print("options", options);
//print("Memory before drawing", process.memory(false));
g.drawImage(image ,offset.X + pos.X,offset.Y + pos.Y, options);
g.drawImage(image ,(imageOffset.X ? imageOffset.X : 0) + (pos.X ? pos.X : 0),(imageOffset.Y ? imageOffset.Y :0) + (pos.Y ? pos.Y : 0), options);
} else {
//print("Could not create image from", resource);
}
@ -153,12 +219,30 @@ function checkRedraw(element, newValue){
}
}
function getValue(value, defaultValue){
if (typeof value == "string"){
return numbers[value]();
}
if (value == undefined) return defaultValue;
return value;
}
function getMultistate(name, defaultValue){
if (typeof name == "string"){
return multistates[name]();
} else {
if (name == undefined) return defaultValue;
}
return undefined;
}
function drawScale(scale, offset){
//print("drawScale", scale, offset);
var segments = scale.Segments;
var value = numbers[scale.Value]();
var maxValue = scale.MaxValue ? scale.MaxValue : 1;
var minValue = scale.MinValue ? scale.MinValue : 0;
var maxValue = getValue(scale.MaxValue, 1);
var minValue = getValue(scale.MinValue, 0);
var imageIndex = scale.ImageIndex !== undefined ? scale.ImageIndex : 0;
value = value/maxValue;
value -= minValue;
@ -171,7 +255,7 @@ function drawScale(scale, offset){
if (checkRedraw(scale, segmentsToDraw)){
for (var i = 0; i < segmentsToDraw; i++){
drawElement(segments[i], scaleOffset, scale.ImagePath, i);
drawElement(segments[i], scaleOffset, scale.ImagePath, imageIndex + i);
}
scale.lastDrawnValue = segmentsToDraw;
}
@ -182,20 +266,22 @@ function drawDigit(element, offset, digit){
}
function drawImage(image, offset, name){
var imageOffset = updateColors(image, offset);
if (image.ImagePath) {
//print("drawImage", image, offset, name);
offset = updateColors(image, offset);
if (image.Value && image.Steps){
var steps = Math.floor(scaledown(numbers[image.Value](), image.MinValue, image.MaxValue) * (image.Steps - 1));
var steps = Math.floor(scaledown(getValue(image.Value), getValue(image.MinValue, 0), getValue(image.MaxValue, 1)) * (image.Steps - 1));
//print("Step", steps, "of", image.Steps);
drawElement(image, offset, image.ImagePath, "" + steps);
drawElement(image, imageOffset, image.ImagePath, "" + steps);
} else if (image.ImageIndex !== undefined) {
drawElement(image, imageOffset, image.ImagePath, image.ImageIndex);
} else {
drawElement(image, offset, image.ImagePath, name ? "" + name: undefined);
drawElement(image, imageOffset, image.ImagePath, name ? "" + name: undefined);
}
} else if (image.ImageFile) {
var file = require("Storage").readJSON(image.ImageFile);
setColors(offset);
g.drawImage(prepareImg(file),image.X + offset.X, image.Y + offsetY);
setColors(imageOffset);
g.drawImage(prepareImg(file),image.X + imageOffset.X, image.Y + imageOffset.Y);
}
}
@ -257,32 +343,35 @@ function getWeatherTemperature(){
}
function updateOffset(element, offset){
var newOffset = { X: offset.X, Y: offset.Y };
if (element.X) newOffset.X += element.X;
if (element.Y) newOffset.Y += element.Y;
var newOffset = { X: offset.X ? offset.X : 0, Y: offset.Y ? offset.Y : 0 };
if (element && element.X) newOffset.X += element.X;
if (element && element.Y) newOffset.Y += element.Y;
newOffset = updateColors(element, newOffset);
//print("Updated offset from ", offset, "to", newOffset);
return newOffset;
}
function updateColors(element, offset){
var newOffset = { X: offset.X, Y: offset.Y };
newOffset.fg = element.ForegroundColor ? element.ForegroundColor: offset.fg;
newOffset.bg = element.BackgroundColor ? element.BackgroundColor: offset.bg;
var newOffset = { X: offset.X ? offset.X : 0, Y: offset.Y ? offset.Y : 0 };
if (element){
newOffset.fg = element.ForegroundColor ? element.ForegroundColor: offset.fg;
newOffset.bg = element.BackgroundColor ? element.BackgroundColor: offset.bg;
}
//print("Updated offset from ", offset, "to", newOffset);
return newOffset;
}
function scaledown(value, min, max){
print("scaledown", value, min, max);
var scaled = E.clip(value,min?min:0,max?max:1);
scaled -= min?min:0;
scaled /= max?max:1;
//print("scaledown", value, min, max);
var scaled = E.clip(value,getValue(min,0),getValue(max,1));
scaled -= getValue(min,0);
scaled /= getValue(max,1);
return scaled;
}
function rotate(center, coords, rotation) {
var value = scaledown(numbers[rotation.RotationValue](), rotation.MinRotationValue, rotation.MaxRotationValue);
value -= rotation.RotationOffset ? rotation.RotationOffset : 0;
value *= 360;
value -= 180;
@ -298,7 +387,9 @@ function rotate(center, coords, rotation) {
function drawPoly(element, offset){
var vertices = [];
var primitiveOffset = offset;
var primitiveOffset = offset.clone();
if (element.X) primitiveOffset.X += element.X;
if (element.Y) primitiveOffset.Y += element.Y;
for (var c of element.Vertices){
if (element.RotationValue){
@ -313,7 +404,7 @@ function drawPoly(element, offset){
if (element.ForegroundColor) g.setColor(element.ForegroundColor);
if (element.Filled ){
if (element.Filled){
g.fillPoly(vertices,true);
}
@ -337,6 +428,8 @@ numbers.Second = () => { return new Date().getSeconds(); };
numbers.SecondAnalog = () => { var date = new Date(); return date.getSeconds() + (date.getMilliseconds()/999); };
numbers.SecondTens = () => { return Math.floor(new Date().getSeconds()/10); };
numbers.SecondOnes = () => { return Math.floor(new Date().getSeconds()%10); };
numbers.WeekDay = () => { return new Date().getDay(); };
numbers.WeekDayMondayFirst = () => { var day = (new Date().getDay() - 1); if (day < 0) day = 7 + day; return day; };
numbers.Day = () => { return new Date().getDate(); };
numbers.DayTens = () => { return Math.floor(new Date().getDate()/10); };
numbers.DayOnes = () => { return Math.floor(new Date().getDate()%10); };
@ -345,13 +438,14 @@ numbers.MonthTens = () => { return Math.floor((new Date().getMonth() + 1)/10); }
numbers.MonthOnes = () => { return Math.floor((new Date().getMonth() + 1)%10); };
numbers.Pulse = () => { return pulse; };
numbers.Steps = () => { return Bangle.getHealthStatus ? Bangle.getHealthStatus("day").steps : undefined; };
numbers.StepsGoal = () => { return stepsgoal; };
numbers.Temperature = () => { return temp; };
numbers.Pressure = () => { return press; };
numbers.Altitude = () => { return alt; };
numbers.BatteryPercentage = E.getBattery;
numbers.BatteryVoltage = NRF.getBattery;
numbers.WeatherCode = getWeatherCode;
numbers.WeatherTemperature = () => { var t = getWeatherTemperature().value; return t ? t : undefined; };
numbers.WeatherTemperature = () => { return getWeatherTemperature().value; };
var multistates = {};
multistates.Lock = () => { return Bangle.isLocked() ? "on" : "off"; };
@ -365,7 +459,9 @@ multistates.HRM = () => { return Bangle.isHRMOn ? "on" : "off"; };
multistates.Barometer = () => { return Bangle.isBarometerOn() ? "on" : "off"; };
multistates.Compass = () => { return Bangle.isCompassOn() ? "on" : "off"; };
multistates.GPS = () => { return Bangle.isGPSOn() ? "on" : "off"; };
multistates.WeatherTemperatureNegative = () => { return getWeatherTemperature().value ? getWeatherTemperature().value : 0 < 0; };
multistates.WeatherTemperatureUnit = () => { return getWeatherTemperature().unit; };
multistates.StepsGoal = () => { return (numbers.Steps() >= stepsgoal) ? "on": "off"; };
function drawMultiState(element, offset){
//print("drawMultiState", element, offset);
@ -381,6 +477,9 @@ function draw(element, offset){
var initial = !element;
if (initial){
element = face;
if (!offset) offset ={};
if (!offset.X) offset.X = 0;
if (!offset.Y) offset.Y = 0;
g.clear();
}
@ -461,7 +560,7 @@ function initialDraw(){
function handleHrm(e){
if (e.confidence > 70){
pulse = e.bpm;
if (!redrawEvents || redrawEvents.includes("HRM")){
if (!redrawEvents || redrawEvents.includes("HRM") && !Bangle.isLocked()){
//print("Redrawing on HRM");
initialDraw();
}
@ -472,14 +571,14 @@ function handlePressure(e){
alt = e.altitude;
temp = e.temperature;
press = e.pressure;
if (!redrawEvents || redrawEvents.includes("pressure")){
if (!redrawEvents || redrawEvents.includes("pressure") && !Bangle.isLocked()){
//print("Redrawing on pressure");
initialDraw();
}
}
function handleCharging(e){
if (!redrawEvents || redrawEvents.includes("charging")){
if (!redrawEvents || redrawEvents.includes("charging") && !Bangle.isLocked()){
//print("Redrawing on charging");
initialDraw();
}
@ -512,6 +611,8 @@ var defaultRedraw = getByPath(face, ["Properties","Redraw","Default"]) || "Alway
var redrawEvents = getByPath(face, ["Properties","Redraw","Events"]);
var events = getByPath(face, ["Properties","Events"]);
var stepsgoal = 2000;
//print("events", events);
//print("redrawEvents", redrawEvents);

View File

@ -11,12 +11,22 @@
<h4>Upload watchface:</h4>
<p>
Select format:</br>
<input type="radio" id="useNative" name="mode" checked/>
<label for="useNative">Native</label></br>
<input type="radio" id="useAmazefit" name="mode"/>
<label for="useAmazefit">ALPHA: Decompiled Amazfit</label></br>
</p>
<p>Select watchface folder:</br><input type="file" id="fileLoader" name="files[]" multiple directory="" webkitdirectory="" moxdirectory="" /></p>
<p><b>or</b></p>
<p>Select watchface zip file: </br><input type="file" id="zipLoader" name="zip"/></p><br/>
<button id="btnUpload" class="btn btn-primary">Upload to watch</button>
<button id="btnUpload" class="btn btn-primary">Upload to watch</button></br>
<button id="btnSave" class="btn btn-secondary">Save resources file</button></br>
<button id="btnSaveFace" class="btn btn-secondary">Save face file</button></br>
<button id="btnSaveZip" class="btn btn-secondary">Save watchface zip file</button></br>
<canvas id="canvas" style="display:none;"></canvas></br>
<p>Download Demo Watchface here: </br>
<a href="digitalretro.zip">digitalretro.zip</a></br>
@ -30,6 +40,468 @@
var faceJson;
var handledFiles = 0;
var expectedFiles = 0;
var rootZip = new JSZip();
var resourcesZip = rootZip.folder("resources");
function isNativeFormat(){
return document.getElementById("useNative").checked;
}
function convertAmazfitTime(time){
var result = {};
if (time.Hours){
result.Hours = {
Tens: convertAmazfitNumber(time.Hours.Tens, "HourTens", 0, 10),
Ones: convertAmazfitNumber(time.Hours.Ones, "HourOnes", 0, 10)
}
result.Hours.Tens.Number.Alignment = "TopLeft";
result.Hours.Tens.Number.Spacing = 0;
result.Hours.Ones.Number.Alignment = "TopLeft";
result.Hours.Ones.Number.Spacing = 0;
}
if (time.Minutes){
result.Minutes = {
Tens: convertAmazfitNumber(time.Minutes.Tens, "MinuteTens", 0, 10),
Ones: convertAmazfitNumber(time.Minutes.Ones, "MinuteOnes", 0, 10)
}
result.Minutes.Tens.Number.Alignment = "TopLeft";
result.Minutes.Tens.Number.Spacing = 0;
result.Minutes.Ones.Number.Alignment = "TopLeft";
result.Minutes.Ones.Number.Spacing = 0;
}
return result;
}
function convertAmazfitDate(date){
var result = {};
if (date.MonthAndDay.Separate.Day) result.Day = convertAmazfitNumber(date.MonthAndDay.Separate.Day, "Day");
if (date.MonthAndDay.Separate.Month) result.Month = convertAmazfitNumber(date.MonthAndDay.Separate.Month, "Month");
if (date.WeekDay){
result.WeekDay = convertAmazfitNumber(date.WeekDay, "WeekDayMondayFirst");
}
return result;
}
var filesToMove={};
var zipChangePromise = Promise.resolve();
function performFileChanges(){
var promise = Promise.resolve();
//rename all files to just numbers without leading zeroes
for (var c in resultJson){
console.log("Renaming", c, resultJson[c]);
var tmp = resultJson[c];
delete resultJson[c];
resultJson[Number(c)] = tmp;
async function modZip(c){
console.log("Async modification of ", c)
var fileRegex = new RegExp(c + ".*");
var fileObject = resourcesZip.file(fileRegex)[0];
var fileData = await fileObject.async("uint8array");
console.log("Filedata is", fileData);
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
var newName = Number(c) + extension;
console.log("Renaming to", newName);
resourcesZip.remove(c + extension);
resourcesZip.file(newName, fileData);
}
promise = promise.then(modZip(c));
}
console.log("File moves:", filesToMove);
for (var c in filesToMove){
var tmp = resultJson[c];
console.log("Handle filemove", c, filesToMove[c], tmp);
var element = resultJson;
var path = filesToMove[c];
async function modZip(c){
console.log("Async modification of ", c)
var fileRegex = new RegExp(c + ".*");
var fileObject = resourcesZip.file(fileRegex)[0];
var fileData = await fileObject.async("uint8array");
console.log("Filedata is", fileData);
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
var newName = Number(c) + extension;
console.log("Copying to", newName);
resourcesZip.file(filesToMove[c].join("/") + extension, fileData);
}
promise = promise.then(modZip(c));
for (var i = 0; i< path.length; i++){
if (!element[path[i]]) element[path[i]] = {};
if (i == path.length - 1){
element[path[i]] = tmp;
} else {
element = element[path[i]];
}
}
}
promise.then(()=>{
document.getElementById('btnUpload').disabled = true;
});
console.log("After moves", resultJson);
return promise;
};
function convertAmazfitMultistate(multistate, value, minValue, maxValue){
var result = {
MultiState: {
X: multistate.Coordinates.X,
Y: multistate.Coordinates.Y,
Value: value,
ImagePath: [value]
}
};
if (minValue !== undefined) result.MultiState.MinValue = minValue;
if (maxValue !== undefined) result.MultiState.MaxValue = maxValue;
if (multistate.ImageIndexOn) filesToMove[multistate.ImageIndexOn] = ["status", value, "vibrate"];
if (multistate.ImageIndexOff) filesToMove[multistate.ImageIndexOff] = ["status", value, "off"];
return result;
}
function convertAmazfitStatus(status){
var result = {};
if (status.Alarm) result.Alarm = convertAmazfitMultistate(status.Alarm,"Alarm");
if (status.Bluetooth) result.Bluetooth = convertAmazfitMultistate(status.Bluetooth,"Bluetooth");
if (status.DoNotDisturb) result.DoNotDisturb = convertAmazfitMultistate(status.DoNotDisturb,"Notifications");
if (status.Lock) result.Lock = convertAmazfitMultistate(status.Lock,"Lock");
return result;
}
function convertAmazfitNumber(element, value, minValue, maxValue){
var number = {};
var result = {
Number: number
};
if (element.Alignment == "BottomRight"){
number.X = element.BottomRightX;
number.Y = element.BottomRightY;
} else if (element.Alignment == "TopLeft"){
number.X = element.TopLeftX;
number.Y = element.TopLeftY;
} else if (element.Alignment == "TopRight"){
number.X = element.BottomRightX;
number.Y = element.TopLeftY;
} else if (element.Alignment == "BottomLeft"){
number.X = element.TopLeftX;
number.Y = element.BottomLeftY;
} else if (element.Alignment == "Center"){
number.X = (element.TopLeftX + (element.BottomRightX - element.TopLeftX)/2),
number.Y = (element.TopLeftY + (element.BottomRightY - element.TopLeftY)/2)
} else {
number.X = element.X,
number.Y = element.Y
}
number.Alignment = element.Alignment;
number.Spacing = element.Spacing;
number.ImageIndex = element.ImageIndex;
number.ImagePath = [];
number.Value = value;
if (minValue !== undefined) number.MaxValue = maxValue;
if (maxValue !== undefined) number.MinValue = minValue;
return result;
}
function moveWeatherIcons(icon){
filesToMove[icon.ImageIndex + 0] = ["weather", "fallback"];
// Light clouds
filesToMove[icon.ImageIndex + 1] = ["weather", 801];
// Cloudy, possible rain
filesToMove[icon.ImageIndex + 2] = ["weather", 500];
// Cloudy, possible snow
filesToMove[icon.ImageIndex + 3] = ["weather", 600];
// Clear
filesToMove[icon.ImageIndex + 4] = ["weather", 800];
// Clouds
filesToMove[icon.ImageIndex + 5] = ["weather", 803];
// Light Rain
filesToMove[icon.ImageIndex + 6] = ["weather", 501];
// Light Snow
filesToMove[icon.ImageIndex + 7] = ["weather", 601];
// Rain
filesToMove[icon.ImageIndex + 8] = ["weather", 502];
// Snow
filesToMove[icon.ImageIndex + 9] = ["weather", 602];
// Heavy Snow
filesToMove[icon.ImageIndex + 10] = ["weather", 621];
// Heavy Rain
filesToMove[icon.ImageIndex + 11] = ["weather", 503];
// Sandstorm
filesToMove[icon.ImageIndex + 12] = ["weather", 751];
// Snow and Rain
filesToMove[icon.ImageIndex + 13] = ["weather", 616];
// Fog
filesToMove[icon.ImageIndex + 14] = ["weather", 741];
// Mist
filesToMove[icon.ImageIndex + 15] = ["weather", 701];
// Shower
filesToMove[icon.ImageIndex + 16] = ["weather", 521];
// Hail
filesToMove[icon.ImageIndex + 17] = ["weather", 611];
// Hailstorm
filesToMove[icon.ImageIndex + 18] = ["weather", 613];
// Heavy Shower
filesToMove[icon.ImageIndex + 19] = ["weather", 522];
// Dust whirls
filesToMove[icon.ImageIndex + 20] = ["weather", 731];
// Tornado
filesToMove[icon.ImageIndex + 21] = ["weather", 781];
// Very heavy shower
filesToMove[icon.ImageIndex + 22] = ["weather", 531];
}
function convertAmazfitTemperature(temp){
var result = {};
result = convertAmazfitNumber(temp.Number, "WeatherTemperature");
if (temp.MinusImageIndex){
result.Number.ImageIndexMinus = temp.MinusImageIndex;
}
if (temp.DegreesImageIndex){
result.Number.ImageIndexUnit = temp.DegreesImageIndex;
}
return result;
}
function convertAmazfitWeather(weather){
var result = {};
if (weather.Temperature && weather.Temperature.Current){
if (!result.Temperature) result.Temperature = {};
result.Temperature.Current = convertAmazfitTemperature(weather.Temperature.Current);
}
if (weather.Temperature && weather.Temperature.Today){
if (!result.Temperature) result.Temperature = {};
if (weather.Temperature.Today.Separate){
if (weather.Temperature.Today.Separate.Day){
result.Temperature.Day = convertAmazfitTemperature(weather.Temperature.Today.Separate.Day);
}
if (weather.Temperature.Today.Separate.Night){
result.Temperature.Night = convertAmazfitTemperature(weather.Temperature.Today.Separate.Night);
}
}
}
if (weather.Icon){
result.WeatherIcon = {
CodedImage: {
X: weather.Icon.CustomIcon.X,
Y: weather.Icon.CustomIcon.Y,
Value: "WeatherCode",
ImagePath: ["weather"]
}
}
moveWeatherIcons(weather.Icon.CustomIcon);
}
return result;
}
function convertAmazfitActivity(activity){
var result = {};
if (activity.Steps){
result.Steps = convertAmazfitNumber(activity.Steps, "Steps");
}
if (activity.Pulse){
result.Pulse = convertAmazfitNumber(activity.Pulse, "Pulse");
}
return result;
}
function convertAmazfitScale(scale, value, minValue, maxValue){
var result = {};
result.Scale = {
ImageIndex: scale.StartImageIndex,
ImagePath: [],
Value: value,
MaxValue: maxValue,
MinValue: minValue
};
result.Scale.Segments = [];
for (var c of scale.Segments){
result.Scale.Segments.push({
X: c.X,
Y: c.Y
});
}
return result;
}
function convertAmazfitStepsProgress(steps){
var result = {};
if (steps.GoalImage){
result.Goal = {
MultiState: {
X: steps.GoalImage.X,
Y: steps.GoalImage.Y,
Value: "StepsGoal",
ImagePath: ["StepsGoal"]
}
}
filesToMove[steps.GoalImage.ImageIndex] = ["StepsGoal", "on"];
}
if (steps.Linear){
result.Scale = convertAmazfitScale(steps.Linear, "Steps", 0, "StepsGoal").Scale;
}
return result;
}
function convertAmazfitBattery(battery){
var result = {};
if (battery.Scale){
result.Scale = convertAmazfitScale(battery.Scale, "BatteryPercentage", 0, 100).Scale;
}
if (battery.Text){
result.Number = convertAmazfitNumber(battery.Text, "BatteryPercentage", 0, 100).Number;
}
return result;
}
function convertAmazfitImage(image){
var result = {
Image: {
X: image.X,
Y: image.Y,
ImagePath: [],
ImageIndex: image.ImageIndex
}
};
return result;
}
function convertAmazfitColor(color){
return "#" + color.substring(2);
}
function convertAmazfitHand(hand, rotationValue, minRotationValue, maxRotationValue){
var result = {
Filled: !hand.OnlyBorder,
X: hand.Center.X,
Y: hand.Center.Y,
ForegroundColor: convertAmazfitColor(hand.Color),
BackgroundColor: convertAmazfitColor(hand.Color),
RotationValue: rotationValue,
RotationOffset: 0.25,
MaxRotationValue: maxRotationValue,
MinRotationValue: minRotationValue
};
result.Vertices = []
for (var c of hand.Shape){
result.Vertices.push(c);
}
return { Poly: result };
}
function convertAmazfitAnalog(analog){
var result = {
};
if (analog.Hours){
result.Hours = {};
result.Hours.Hand = convertAmazfitHand(analog.Hours, "Hour12Analog", 0, 12);
if (analog.Hours.CenterImage){
result.Hours.Center = convertAmazfitImage(analog.Hours.CenterImage);
}
}
if (analog.Minutes){
result.Minutes = {};
result.Minutes.Hand = convertAmazfitHand(analog.Minutes, "Minute", 0, 60);
if (analog.Minutes.CenterImage){
result.Minutes.Center = convertAmazfitImage(analog.Minutes.CenterImage);
}
}
if (analog.Seconds){
result.Seconds = {};
result.Seconds = convertAmazfitHand(analog.Seconds, "Second", 0, 60);
if (!faceJson.Properties) faceJson.Properties = {};
if (!faceJson.Properties.Redraw) faceJson.Properties.Redraw = {};
if (!faceJson.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Unlocked = 1000;
if (!faceJson.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Locked = 15000;
if (analog.Seconds.CenterImage){
result.Seconds.Center = convertAmazfitImage(analog.Seconds.CenterImage);
}
}
return result;
}
function restructureAmazfitFormat(dataString){
console.log("Amazfit data:", dataString);
var json = JSON.parse(dataString);
faceJson = json;
if (!json.Properties) faceJson.Properties = {};
if (!json.Properties.Redraw) faceJson.Properties.Redraw = {};
if (!json.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Unlocked = 10000;
if (!json.Properties.Redraw.Unlocked) faceJson.Properties.Redraw.Locked = 60000;
var result = {};
if (json.Background){
result.Background = json.Background;
result.Background.Image.ImagePath = [];
delete result.Background.Preview;
}
if (json.Time){
result.Time = convertAmazfitTime(json.Time);
}
if (json.Date){
result.Date = convertAmazfitDate(json.Date);
}
if (json.Status){
result.Status = convertAmazfitStatus(json.Status);
}
if (json.Weather){
result.Weather = convertAmazfitWeather(json.Weather);
}
if (json.Activity){
result.Activity = convertAmazfitActivity(json.Activity);
}
if (json.StepsProgress){
result.StepsProgress = convertAmazfitStepsProgress(json.StepsProgress);
}
if (json.Battery){
result.Battery = convertAmazfitBattery(json.Battery);
}
if (json.AnalogDialFace){
result.Analog = convertAmazfitAnalog(json.AnalogDialFace);
}
console.log("Converted to native:", result);
return result;
}
function parseFaceJson(jsonString){
if (isNativeFormat()){
return JSON.parse(jsonString);
} else {
return restructureAmazfitFormat(jsonString);
}
}
function imageLoaded() {
var options = {};
@ -37,7 +509,7 @@
options.diffusion = infoJson.diffusion ? infoJson.diffusion : "none";
options.compression = infoJson.compression ? infoJson.compression : false;
options.alphaToColor = false;
options.transparent = false;
options.transparent = infoJson.transparent ? infoJson.transparent : false;
options.inverted = false;
options.autoCrop = false;
options.brightness = 0;
@ -45,7 +517,6 @@
options.mode = infoJson.color ? infoJson.color : "1bit";
options.output = "jsonobject";
var faceJson;
console.log("Loaded image has path", this.path);
var jsonPath = this.path.split("/");
@ -111,26 +582,45 @@
console.log("Expected:", expectedFiles, " handled:", handledFiles);
if (handledFiles == expectedFiles){
document.getElementById('btnSave').disabled = false;
document.getElementById('btnUpload').disabled = false;
if (!isNativeFormat()) {
performFileChanges().then(()=>{
rootZip.file("face.json", JSON.stringify(faceJson, null, 2));
rootZip.file("info.json", JSON.stringify(infoJson, null, 2));
document.getElementById('btnSave').disabled = false;
document.getElementById('btnSaveFace').disabled = false;
document.getElementById('btnSaveZip').disabled = false;
document.getElementById('btnUpload').disabled = false;
});
} else {
document.getElementById('btnSave').disabled = false;
document.getElementById('btnSaveFace').disabled = false;
document.getElementById('btnUpload').disabled = false;
}
}
}
function handleWatchFace(infoFile, faceFile, resourceFiles){
var reader = new FileReader();
reader.path = infoFile.webkitRelativePath;
reader.onload = function(event) {
infoJson = JSON.parse(reader.result);
if (isNativeFormat()){
var reader = new FileReader();
reader.path = infoFile.webkitRelativePath;
reader.onload = function(event) {
infoJson = JSON.parse(reader.result);
handleFaceJson(faceFile, resourceFiles);
};
reader.readAsText(infoFile);
} else {
console.log("Handling amazfit watch face");
handleFaceJson(faceFile, resourceFiles);
};
reader.readAsText(infoFile);
}
}
function handleFaceJson(faceFile, resourceFiles){
var reader = new FileReader();
reader.path = faceFile.webkitRelativePath;
reader.onload = function(event) {
faceJson = JSON.parse(reader.result);
faceJson = parseFaceJson(reader.result);
handleResourceFiles(resourceFiles);
};
reader.readAsText(faceFile);
@ -141,8 +631,14 @@
console.log('Handle resource file ', current);
var reader = new FileReader();
console.log("Handling ", current.name, " with path ", current.webkitRelativePath);
var filteredPath = current.webkitRelativePath.replace(/.*\/resources\//,"")
var filteredPath;
if (isNativeFormat()){
filteredPath = current.webkitRelativePath.replace(/.*\/resources\//,"");
} else {
filteredPath = current.webkitRelativePath.replace(/.*\//,"");
}
reader.path = filteredPath;
resourcesZip.file(filteredPath, current);
reader.onload = function(event) {
var img = new Image();
img.path = this.path;
@ -158,6 +654,8 @@
expectedFiles = undefined;
document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnUpload').disabled = true;
console.log("File select event", event);
@ -171,18 +669,36 @@
for (var current of event.target.files){
console.log('Handle file ', current);
if (current.webkitRelativePath.split("/")[1].startsWith("resources")){
console.log('Found resource file', current.name);
resourceFiles.push(current);
expectedFiles = resourceFiles.length;
} else if (current.name == "face.json"){
console.log('Found face file', current.name);
faceFile = current;
} else if (current.name == "info.json"){
console.log('Found info file', current.name);
infoFile = current;
if (isNativeFormat()){
if (current.webkitRelativePath.split("/")[1].startsWith("resources")){
console.log('Found resource file', current.name);
resourceFiles.push(current);
expectedFiles = resourceFiles.length;
} else if (current.name == "face.json"){
console.log('Found face file', current.name);
faceFile = current;
} else if (current.name == "info.json"){
console.log('Found info file', current.name);
infoFile = current;
} else {
console.log('Found unsupported file', current.name);
}
} else {
console.log('Found unsupported file', current.name);
infoJson = {
"color": "3bit",
"compression": true,
"transparent": true
};
if (current.name.includes(".png")){
console.log('Found resource file', current.name);
resourceFiles.push(current);
expectedFiles = resourceFiles.length;
} else if (current.name.includes(".json")){
console.log('Found amazfit file', current.name);
faceFile = current;
} else {
console.log('Found unsupported file', current.name);
}
}
}
handleWatchFace(infoFile, faceFile, resourceFiles);
@ -207,6 +723,8 @@
{name:"imageclock.img", url:"app-icon.js", evaluate:true},
]
};
console.log("Uploading app:", appDef);
sendCustomizedApp(appDef);
});
@ -217,6 +735,8 @@
expectedFiles = 0;
handledFiles = 0;
document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnUpload').disabled = true;
JSZip.loadAsync(f).then(function(zip) {
@ -228,13 +748,22 @@
var promise = zip.file("face.json").async("string").then((data)=>{
console.log("face.json data", data);
faceJson = JSON.parse(data);
faceJson = parseFaceJson(data);
});
promise = promise.then(zip.file("info.json").async("string").then((data)=>{
console.log("info.json data", data);
infoJson = JSON.parse(data);
}));
if (isNativeFormat()){
promise = promise.then(zip.file("info.json").async("string").then((data)=>{
console.log("info.json data", data);
infoJson = JSON.parse(data);
}));
} else {
infoJson = {
"color": "3bit",
"compression": true,
"transparent": true
};
}
zip.folder("resources").forEach(function (relativePath, file){
console.log("iterating over", relativePath);
@ -274,10 +803,30 @@
handleFile(files[0]);
}
function handleZipExport(){
rootZip.generateAsync({type:"base64"}).then(function (base64) {
var h = document.createElement('a');
h.href="data:application/zip;base64," + base64;
h.target = '_blank';
h.download = "watchface.zip";
h.click();
});
}
document.getElementById("btnSaveFace").addEventListener("click", function() {
var h = document.createElement('a');
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(faceJson));
h.target = '_blank';
h.download = "face.json";
h.click();
});
document.getElementById('zipLoader').addEventListener('change', handleZipSelect, false);
document.getElementById('btnSaveZip').addEventListener('click', handleZipExport, false);
document.getElementById('btnSave').disabled = true;
document.getElementById('btnSaveFace').disabled = true;
document.getElementById('btnSaveZip').disabled = true;
document.getElementById('btnUpload').disabled = true;
</script>

View File

@ -2,7 +2,7 @@
"id": "imageclock",
"name": "Imageclock",
"shortName": "imageclock",
"version": "0.02",
"version": "0.03",
"type": "clock",
"description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.",
"icon": "app.png",