Initial commit

This commit is contained in:
CodePoet80 committed 2021-01-25 10:23:32 -05:00
commit a4ceb7ffe3
32 files changed
+1896

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
secrets.js
.DS_Store
+1
View File
@@ -0,0 +1 @@
palm-package "%cd%" -o "./_bin"
Vendored Executable
+3
View File
@@ -0,0 +1,3 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
mkdir -p $DIR/../_bin
palm-package $DIR/../ -o $DIR/../_bin
+1
View File
@@ -0,0 +1 @@
palm-run "%cd%"
Vendored Executable
+2
View File
@@ -0,0 +1,2 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
palm-run $DIR/../
+38
View File
@@ -0,0 +1,38 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "palm-run",
"type": "shell",
"command": "./.vscode/run.sh",
"windows": {
"command": ".\\.vscode\\run.bat"
},
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "dedicated"
}
},
{
"label": "palm-build",
"type": "shell",
"command": "./.vscode/build.sh",
"windows": {
"command": ".\\.vscode\\build.bat"
},
"problemMatcher": [],
"group": "build",
"presentation": {
"reveal": "always",
"panel": "dedicated"
}
}
]
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Jon W
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+7
View File
@@ -0,0 +1,7 @@
# Simple Chat client app for webOS
SimpleChat client for legacy Palm/HP webOS devices, depends on back-end services listed below.
Uses [simplechat-service](https://github.com/codepoet80/simplechat-service)
You can also self-host the services.
+34
View File
@@ -0,0 +1,34 @@
/*
In the app assistant, we setup some app-wide global objects and handle different kinds of launches, creating and delegating to the main stage
*/
var appModel = null;
var updaterModel = null;
var systemModel = null;
function AppAssistant() {
appModel = new AppModel();
updaterModel = new UpdaterModel();
systemModel = new SystemModel();
Mojo.Additions = Additions;
}
//This function will handle relaunching the app when an alarm goes off(see the device/alarm scene)
AppAssistant.prototype.handleLaunch = function(params) {
//get the proxy for the stage in the event it already exists (eg: app is currently open)
var mainStage = this.controller.getStageProxy("");
Mojo.Log.info("SimpleChat is Launching! Launch params: " + JSON.stringify(params));
//if there was a search query, load with that
if (params && params["query"] != undefined) {
appModel.LaunchQuery = decodeURI(params["query"]);
Mojo.Log.info("Launch query was: " + appModel.LaunchQuery);
}
//if the stage already exists then just bring it into focus
if (mainStage) {
var stageController = this.controller.getStageController("");
stageController.activate();
}
return;
};
+433
View File
@@ -0,0 +1,433 @@
/*
SimpleChat client app for webOS.
This app depends on a SimpleChat service, provided by webOS Archive at no cost for what remains of the webOS mobile community.
*/
function MainAssistant() {
/* this is the creator function for your scene assistant object. It will be passed all the
additional parameters (after the scene name) that were passed to pushScene. The reference
to the scene controller (this.controller) has not be established yet, so any initialization
that needs the scene controller should be done in the setup function below. */
}
MainAssistant.prototype.setup = function() {
// setup message field
this.messageFieldModel = {
'original': ''
};
this.messageFieldModel.attributes = {
hintText: $L("Enter a message..."),
enterSubmits: true,
focus: true,
multiline: false,
textCase: Mojo.Widget.steModeSentenceCase,
focusMode: Mojo.Widget.focusSelectMode,
autoReplace: true,
requiresEnterKey: true,
changeOnKeyPress: false
};
this.controller.setupWidget(
'txtMessage',
this.messageFieldModel.attributes,
this.messageFieldModel
);
//Loading spinner - with global members for easy toggling later
this.spinnerAttrs = {
spinnerSize: Mojo.Widget.spinnerSmall
};
this.spinnerModel = {
spinning: false
}
this.controller.setupWidget('workingSpinner', this.spinnerAttrs, this.spinnerModel);
//Chat Log List (starts empty)
this.listElement = this.controller.get('chatList');
this.listInfoModel = {
items: [] //{ uid: "-1", sender: "none", message: "" }
};
//Chat Log List templates (loads other HTML)
this.template = {
itemTemplate: 'main/item-template',
listTemplate: 'main/list-template',
swipeToDelete: false,
renderLimit: 25,
reorderable: false
};
this.controller.setupWidget('chatList', this.template, this.listInfoModel);
//Scroller
this.scrollerModel = {
mode: 'vertical',
weight: 'light',
friction: 'low'
};
this.controller.setupWidget('chatScroller', {}, this.scrollerModel);
this.chatScroller = this.controller.get("chatScroller");
this.scalingFactor = this.controller.window.zoomFactor || 1;
//TODO: This will be different on different devices
this.scaledHeight = Math.floor(Mojo.Environment.DeviceInfo.screenHeight / this.scalingFactor) - 320;
this.chatScroller.style.height = this.scaledHeight + "px";
//Menu
this.appMenuAttributes = { omitDefaultItems: true };
this.appMenuModel = {
label: "Settings",
items: [
Mojo.Menu.editItem,
{ label: "Change Username", command: 'do-Username' },
{ label: "Preferences", command: 'do-Preferences' },
{ label: "About", command: 'do-myAbout' }
]
};
this.controller.setupWidget(Mojo.Menu.appMenu, this.appMenuAttributes, this.appMenuModel);
/* Always on Event handlers */
Mojo.Event.listen(this.controller.get("txtMessage"), Mojo.Event.propertyChange, this.handleSendMessage.bind(this));
Mojo.Event.listen(this.controller.get("chatList"), Mojo.Event.listTap, this.handleListClick.bind(this));
//Check for updates
if (!appModel.UpdateCheckDone) {
appModel.UpdateCheckDone = true;
updaterModel.CheckForUpdate("SimpleChat", this.handleUpdateResponse.bind(this));
}
};
MainAssistant.prototype.handleUpdateResponse = function(responseObj) {
if (responseObj && responseObj.updateFound) {
updaterModel.PromptUserForUpdate(function(response) {
if (response)
updaterModel.InstallUpdate();
}.bind(this));
}
}
MainAssistant.prototype.activate = function(event) {
//Load preferences
appModel.LoadSettings();
Mojo.Log.info("settings now: " + JSON.stringify(appModel.AppSettingsCurrent));
//Figure out server info
this.serviceEndpointBase = appModel.ServiceEndpointBase;
if (appModel.AppSettingsCurrent["UseCustomEndpoint"] && appModel.AppSettingsCurrent["EndpointURL"]) {
this.serviceEndpointBase = appModel.AppSettingsCurrent["EndpointURL"];
}
this.clientId = appModel.ClientId;
if (appModel.AppSettingsCurrent["UseClientAPIKey"] && appModel.AppSettingsCurrent["ClientAPIKey"]) {
this.clientId = appModel.AppSettingsCurrent["ClientAPIKey"];
}
Mojo.Log.warn("*** Using secret: " + this.clientId + " because secrets were: " + secrets.clientid);
//Figure out if this is our first time
if (appModel.AppSettingsCurrent["FirstRun"]) {
appModel.AppSettingsCurrent["FirstRun"] = false;
var pretendEvent = {
type: Mojo.Event.command,
command: 'do-Username'
}
this.handleCommand(pretendEvent);
} else {
this.controller.get('txtMessage').mojo.focus();
}
//Find out what kind of device this is
if (Mojo.Environment.DeviceInfo.platformVersionMajor >= 3) {
this.DeviceType = "TouchPad";
Mojo.Log.info("Device detected as TouchPad");
} else {
if (window.screen.width == 800 || window.screen.height == 800) {
this.DeviceType = "Pre3";
Mojo.Log.info("Device detected as Pre3");
} else if ((window.screen.width == 480 || window.screen.height == 480) && (window.screen.width == 320 || window.screen.height == 320)) {
this.DeviceType = "Pre";
Mojo.Log.warn("Device detected as Pre or Pre2");
} else {
this.DeviceType = "Tiny";
Mojo.Log.warn("Device detected as Pixi or Veer");
}
}
systemModel.PreventDisplaySleep();
this.pendingMessages = [];
this.firstPoll = true;
this.startPollingServer();
};
MainAssistant.prototype.startPollingServer = function() {
this.getChats();
var useInt = 10000;
if (appModel.AppSettingsCurrent["ForegroundUpdate"])
useInt = appModel.AppSettingsCurrent["ForegroundUpdate"];
this.FileCheckInt = setInterval(this.getChats.bind(this), useInt);
}
MainAssistant.prototype.pausePollingServer = function() {
this.getChats();
clearInterval(this.FileCheckInt);
}
//Handle menu and button bar commands
MainAssistant.prototype.handleCommand = function(event) {
if (event.type == Mojo.Event.command) {
switch (event.command) {
case 'do-Username':
this.pausePollingServer();
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
this.controller.showDialog({
template: 'user/user-scene',
assistant: new UserAssistant(this, function(val) {
Mojo.Log.error("got value from dialog: " + val);
this.startPollingServer();
//this.handleDialogDone(val);
}.bind(this)) //since this will be a dialog, not a scene, it must be defined in sources.json without a 'scenes' member
});
}
break;
case 'do-Preferences':
this.pausePollingServer();
var stageController = Mojo.Controller.stageController;
stageController.pushScene({ name: "preferences", disableSceneScroller: false });
break;
case 'do-myAbout':
Mojo.Additions.ShowDialogBox("SimpleChat - " + Mojo.Controller.appInfo.version, "SimpleChat client for webOS. Copyright 2021, Jon Wise. Distributed under an MIT License.<br>Source code available at: https://github.com/codepoet80/webos-simplechat");
break;
}
}
};
//Handle mojo button taps
MainAssistant.prototype.handleClick = function(event) {
//Nothing to do right now
}
MainAssistant.prototype.handleSendMessage = function(event) {
this.controller.get('txtMessage').mojo.blur();
var newMessage = this.controller.get('txtMessage').mojo.getValue();
this.disableUI();
this.postChatToService(appModel.AppSettingsCurrent["SenderName"], newMessage, "", function(response) {
this.controller.get('txtMessage').mojo.setValue("");
this.enableUI();
try {
var responseObj = JSON.parse(response);
if (responseObj.error) {
//Handle error
Mojo.Log.error("Server error returned: " + responseObj.error);
} else {
Mojo.Log.info("message went through: " + responseObj.posted);
if (responseObj.posted && responseObj.posted != "") {
var newMsg = {
uid: responseObj.posted,
sender: appModel.AppSettingsCurrent["SenderName"],
message: newMessage,
timestamp: this.convertTimeStamp(new Date(), false),
color: "gray"
};
this.pendingMessages.push(responseObj.posted);
Mojo.Log.info("message:" + JSON.stringify(newMsg));
var thisWidgetSetup = this.controller.getWidgetSetup("chatList");
thisWidgetSetup.model.items.push(newMsg);
this.controller.modelChanged(thisWidgetSetup.model);
this.chatScroller.mojo.revealBottom();
this.chatScroller.mojo.adjustBy(0, -200);
}
}
} catch (error) {
//Handle error
}
}.bind(this));
}
//Handle list item taps
MainAssistant.prototype.handleListClick = function(event) {
Mojo.Log.info("Item tapped: " + event.item.uid);
return false;
}
//Send a search request to MeTube to send to Google for us (never worry about HTTPS encryption again)
MainAssistant.prototype.getChats = function() {
this.getChatsFromService("", function(response) {
if (response != null && response != "") {
var responseObj = JSON.parse(response);
if (responseObj.status == "error") {
Mojo.Log.error("Error message from server while fetch chats: " + responseObj.msg);
Mojo.Additions.ShowDialogBox("Server Error", "The server responded to the chat request with: " + responseObj.msg.replace("ERROR: ", ""));
} else {
if (responseObj.messages && responseObj.messages.length > 0) {
this.updateChatsList(responseObj.messages);
} else {
Mojo.Log.warn("Search results were empty. This is unlikely; server, API or connectivity problem possible");
Mojo.Additions.ShowDialogBox("No results", "The server did not report any matches for the search.");
}
}
} else {
Mojo.Log.error("No usable response from server while fetching chats: " + response);
Mojo.Additions.ShowDialogBox("Server Error", "The server did not answer with a usable response to the chat request. Check network connectivity and/or self-host settings.");
}
}.bind(this));
}
//Update the UI with search results from Search Request
MainAssistant.prototype.updateChatsList = function(results) {
Mojo.Log.info("Updating chat list widget with " + results.length + " results!");
var thisWidgetSetup = this.controller.getWidgetSetup("chatList");
//figure out what messages we already have
var knownMessages = [];
var noLongerPending = [];
for (var j = 0; j < thisWidgetSetup.model.items.length; j++) {
knownMessages.push(thisWidgetSetup.model.items[j].uid);
}
//find out if there are new or updates messages
var listUpdated = -1;
for (var i = 0; i < results.length; i++) {
if (knownMessages.indexOf(results[i].uid) == -1) { //brand new message
listUpdated = 1;
thisWidgetSetup.model.items.push({
uid: results[i].uid,
sender: results[i].sender,
message: results[i].message,
timestamp: this.convertTimeStamp(results[i].timestamp, true),
color: "black"
});
} else {
//update a pending message
if (this.pendingMessages.length > 0 && this.pendingMessages.indexOf(results[i].uid) != -1) {
for (var j = 0; j < thisWidgetSetup.model.items.length; j++) {
if (thisWidgetSetup.model.items[j].uid == results[i].uid) {
noLongerPending.push(results[i].uid);
thisWidgetSetup.model.items[j].color = "black";
listUpdated = 0;
}
}
}
}
}
//rebuild pending messages array to remove confirmed messages
if (noLongerPending && noLongerPending.length > 0) {
var newPending = [];
for (var k = 0; k < this.pendingMessages.length; k++) {
if (noLongerPending.indexOf(this.pendingMessages[k]) == -1)
newPending.push(this.pendingMessages[k]);
}
this.pendingMessages = newPending;
Mojo.Log.info("pending messages now: " + this.pendingMessages);
}
//update the UI
if (listUpdated > -1) {
this.controller.modelChanged(thisWidgetSetup.model);
if (listUpdated > 0 && !this.firstPoll) {
systemModel.PlaySound("default_425hz");
}
this.chatScroller.mojo.revealBottom();
this.chatScroller.mojo.adjustBy(0, -200);
this.firstPoll = false;
}
}
MainAssistant.prototype.getChatsFromService = function(serviceURL, callback) {
serviceURL = this.serviceEndpointBase + "get-chat.php";
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", serviceURL);
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
MainAssistant.prototype.postChatToService = function(useSender, useMessage, serviceURL, callback) {
serviceURL = this.serviceEndpointBase + "post-chat.php";
var msgToPost = {
sender: useSender,
message: useMessage
}
Mojo.Log.info("Posting chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", this.clientId);
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
MainAssistant.prototype.convertTimeStamp = function(timeStamp, isUTC) {
if (isUTC) {
var offset = new Date().getTimezoneOffset();
timeStamp = Date.parse(timeStamp);
timeStamp = timeStamp - ((offset * 60) * 1000);
timeStamp = new Date(timeStamp);
}
timeStamp = timeStamp.toLocaleString();
return timeStamp;
}
//Try to make strings easier on tiny devices
MainAssistant.prototype.cleanupString = function(str, mxwl, mxsl) {
str = str.substring(0, mxsl);
str = str.replace(/\s+/g, ' ').trim();
str = str.replace(/[^a-z0-9\s]/gi, '');
return str;
}
MainAssistant.prototype.disableUI = function(statusValue) {
//start spinner
if (!this.spinnerModel.spinning) {
this.spinnerModel.spinning = true;
this.controller.modelChanged(this.spinnerModel);
}
}
MainAssistant.prototype.enableUI = function() {
//stop spinner
this.spinnerModel.spinning = false;
this.controller.modelChanged(this.spinnerModel);
this.controller.get('txtMessage').mojo.focus();
}
MainAssistant.prototype.decodeEntities = function(text) {
var entities = [
['amp', '&'],
['apos', '\''],
['#x27', '\''],
['#x2F', '/'],
['#39', '\''],
['#47', '/'],
['lt', '<'],
['gt', '>'],
['nbsp', ' '],
['quot', '"']
];
for (var i = 0, max = entities.length; i < max; ++i)
text = text.replace(new RegExp('&' + entities[i][0] + ';', 'g'), entities[i][1]);
return text;
}
MainAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
Mojo.Event.stopListening(this.controller.get("chatList"), Mojo.Event.listTap, this.handleListClick);
Mojo.Event.stopListening(this.controller.get("txtMessage"), Mojo.Event.propertyChange, this.handleSendMessage);
this.pausePollingServer();
};
MainAssistant.prototype.cleanup = function(event) {
/* this function should do any cleanup needed before the scene is destroyed as
a result of being popped off the scene stack */
};
+202
View File
@@ -0,0 +1,202 @@
function PreferencesAssistant() {
/* this is the creator function for your scene assistant object. It will be passed all the
additional parameters (after the scene name) that were passed to pushScene. The reference
to the scene controller (this.controller) has not be established yet, so any initialization
that needs the scene controller should be done in the setup function below. */
}
PreferencesAssistant.prototype.setup = function() {
/* setup widgets here */
//Timeout picker
this.controller.setupWidget("listForegroundUpdate",
this.attributes = {
label: $L("Foreground Update"),
choices: [
{ label: "5 seconds", value: 5000 },
{ label: "10 seconds", value: 10000 },
{ label: "30 second", value: 30000 },
{ label: "1 minute", value: 60000 },
{ label: "2 minutes", value: 120000 },
{ label: "5 minutes", value: 180000 },
]
},
this.model = {
value: appModel.AppSettingsCurrent["ForegroundUpdate"],
disabled: false
}
);
this.controller.setupWidget("listBackgroundUpdate",
this.attributes = {
label: $L("Background Update"),
choices: [
{ label: "2 minutes", value: 120000 },
{ label: "5 minutes", value: 300000 },
{ label: "10 minutes", value: 600000 },
{ label: "15 minutes", value: 900000 },
{ label: "30 minutes", value: 1800000 },
{ label: "1 Hour", value: 3600000 },
]
},
this.model = {
value: appModel.AppSettingsCurrent["BackgroundUpdate"],
disabled: true
}
);
//API Toggles
this.controller.setupWidget("toggleClientAPI",
this.attributes = {
trueValue: true,
falseValue: false
},
this.model = {
value: appModel.AppSettingsCurrent["UseClientAPIKey"],
disabled: false
}
);
this.controller.setupWidget("toggleCustomEndPoint",
this.attributes = {
trueValue: true,
falseValue: false
},
this.model = {
value: appModel.AppSettingsCurrent["UseCustomEndpoint"],
disabled: false
}
);
//API Text fields
this.controller.setupWidget("txtClientAPI",
this.attributes = {
hintText: $L("Your Client Id"),
multiline: false,
enterSubmits: false,
autoReplace: false,
textCase: Mojo.Widget.steModeLowerCase
},
this.model = {
value: appModel.AppSettingsCurrent["ClientAPIKey"],
disabled: !appModel.AppSettingsCurrent["UseClientAPIKey"]
}
);
this.controller.setupWidget("txtEndpointURL",
this.attributes = {
hintText: $L("http://your-simplechat-server.com"),
multiline: false,
enterSubmits: false,
autoReplace: false,
textCase: Mojo.Widget.steModeLowerCase
},
this.model = {
value: appModel.AppSettingsCurrent["EndpointURL"],
disabled: !appModel.AppSettingsCurrent["EndpointURL"]
}
);
//OK Button
this.controller.setupWidget("btnOK", { type: Mojo.Widget.activityButton }, { label: "Done", disabled: false });
//Menu
this.appMenuAttributes = { omitDefaultItems: true };
this.appMenuModel = {
label: "Settings",
items: [
Mojo.Menu.editItem,
{ label: "Reset Settings", command: 'do-resetSettings' }
]
};
this.controller.setupWidget(Mojo.Menu.appMenu, this.appMenuAttributes, this.appMenuModel);
/* add event handlers to listen to events from widgets */
Mojo.Event.listen(this.controller.get("listForegroundUpdate"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("listBackgroundUpdate"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("txtClientAPI"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("txtEndpointURL"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("toggleClientAPI"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("toggleCustomEndPoint"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("btnOK"), Mojo.Event.tap, this.okClick.bind(this));
};
PreferencesAssistant.prototype.activate = function(event) {
/* put in event handlers here that should only be in effect when this scene is active. For
example, key handlers that are observing the document */
//this.showBetaFeatures();
};
PreferencesAssistant.prototype.showBetaFeatures = function() {
//No beta features right now
}
PreferencesAssistant.prototype.handleValueChange = function(event) {
Mojo.Log.info(event.srcElement.id + " value changed to " + event.value);
if (event.srcElement.id == "toggleGoogleAPI") {
var thisWidgetSetup = this.controller.getWidgetSetup("txtGoogleAPI");
thisWidgetSetup.model.disabled = !event.value;
this.controller.modelChanged(thisWidgetSetup.model);
if (event.value)
this.controller.get('txtGoogleAPI').mojo.focus();
} else if (event.srcElement.id == "toggleClientAPI") {
var thisWidgetSetup = this.controller.getWidgetSetup("txtClientAPI");
thisWidgetSetup.model.disabled = !event.value;
this.controller.modelChanged(thisWidgetSetup.model);
if (event.value)
this.controller.get('txtClientAPI').mojo.focus();
} else if (event.srcElement.id == "toggleServerKey") {
var thisWidgetSetup = this.controller.getWidgetSetup("txtServerKey");
thisWidgetSetup.model.disabled = !event.value;
this.controller.modelChanged(thisWidgetSetup.model);
if (event.value)
this.controller.get('txtServerKey').mojo.focus();
} else if (event.srcElement.id == "toggleCustomEndPoint") {
var thisWidgetSetup = this.controller.getWidgetSetup("txtEndpointURL");
thisWidgetSetup.model.disabled = !event.value;
this.controller.modelChanged(thisWidgetSetup.model);
if (event.value)
this.controller.get('txtEndpointURL').mojo.focus();
}
//We stashed the preference name in the title of the HTML element, so we don't have to use a case statement
Mojo.Log.info(event.srcElement.title + " now: " + event.value);
appModel.AppSettingsCurrent[event.srcElement.title] = event.value;
appModel.SaveSettings();
//Show/hide beta features
this.showBetaFeatures();
};
//Handle menu and button bar commands
PreferencesAssistant.prototype.handleCommand = function(event) {
if (event.type == Mojo.Event.command) {
switch (event.command) {
case 'do-goBack':
Mojo.Controller.stageController.popScene();
break;
case 'do-resetSettings':
appModel.ResetSettings(appModel.AppSettingsDefaults);
break;
}
}
};
PreferencesAssistant.prototype.okClick = function(event) {
Mojo.Controller.stageController.popScene();
}
PreferencesAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
Mojo.Event.stopListening(this.controller.get("listSearchmax"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("listTimeout"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("txtGoogleAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("txtClientAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleGoogleAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleClientAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleServerKey"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("btnOK"), Mojo.Event.tap, this.okClick.bind(this));
};
PreferencesAssistant.prototype.cleanup = function(event) {
/* this function should do any cleanup needed before the scene is destroyed as
a result of being popped off the scene stack */
};
+11
View File
@@ -0,0 +1,11 @@
Mojo.Additions = Additions;
function StageAssistant() {
/* this is the creator function for your stage assistant object */
}
StageAssistant.prototype.setup = function() {
/* this function is for setup tasks that have to happen when the stage is first created */
this.controller.pushScene({ name: "main" });
this.controller.setWindowOrientation("up");
};
+80
View File
@@ -0,0 +1,80 @@
function UserAssistant(sceneAssistant, doneCallBack) {
this.doneCallBack = doneCallBack;
/* this is the creator function for your scene assistant object. It will be passed all the
additional parameters (after the scene name) that were passed to pushScene. The reference
to the scene controller (this.controller) has not be established yet, so any initialization
that needs the scene controller should be done in the setup function below. */
Mojo.Log.info("new User assistant exists");
this.sceneAssistant = sceneAssistant; //dialog's do not have their own controller, so need access to the launching scene's controller
}
UserAssistant.prototype.setup = function(widget) {
/* this function is for setup tasks that have to happen when the scene is first created */
this.widget = widget;
Mojo.Log.info("User assistant setup");
Mojo.Log.info("Current username is: " + appModel.AppSettingsCurrent["SenderName"]);
/* setup widgets here */
this.sceneAssistant.controller.setupWidget("txtUserName",
this.attributes = {
textFieldName: "userName",
hintText: " Enter nickname",
property: 'value',
multi: false,
enterSubmits: true,
changeOnKeyPress: false,
textReplacement: false,
requiresEnterKey: false,
focus: true
},
this.model = {
value: appModel.AppSettingsCurrent["SenderName"],
disabled: false
}
);
this.sceneAssistant.controller.setupWidget("goButton", { type: Mojo.Widget.activityButton }, { label: "OK", disabled: false });
this.sceneAssistant.controller.setupWidget("cancelButton", { type: Mojo.Widget.button }, { label: "Cancel", disabled: false });
/* add event handlers to listen to events from widgets */
Mojo.Event.listen(this.sceneAssistant.controller.get("txtUserName"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.sceneAssistant.controller.get("goButton"), Mojo.Event.tap, this.handleGoPress.bind(this));
Mojo.Event.listen(this.sceneAssistant.controller.get("cancelButton"), Mojo.Event.tap, this.handleCancelPress.bind(this));
};
UserAssistant.prototype.handleValueChange = function(event) {
this.handleGoPress();
};
UserAssistant.prototype.handleGoPress = function(event) {
var newName = this.sceneAssistant.controller.get('txtUserName').mojo.getValue();
//Remember the address they entered
appModel.AppSettingsCurrent["SenderName"] = newName;
appModel.SaveSettings();
this.doneCallBack(newName);
this.widget.mojo.close();
}
UserAssistant.prototype.handleCancelPress = function(event) {
this.doneCallBack("cancel");
this.widget.mojo.close();
}
UserAssistant.prototype.activate = function(event) {
this.sceneAssistant.controller.get('txtUserName').mojo.focus();
/* put in event handlers here that should only be in effect when this scene is active. For
example, key handlers that are observing the document */
};
UserAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
Mojo.Event.stopListening(this.sceneAssistant.controller.get("txtUserName"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.stopListening(this.sceneAssistant.controller.get("goButton"), Mojo.Event.tap, this.handleGoPress.bind(this));
Mojo.Event.stopListening(this.sceneAssistant.controller.get("cancelButton"), Mojo.Event.tap, this.handleCancelPress.bind(this));
};
UserAssistant.prototype.cleanup = function(event) {
Mojo.Log.info("user assistant cleaned up");
/* this function should do any cleanup needed before the scene is destroyed as
a result of being popped off the scene stack */
};
+126
View File
@@ -0,0 +1,126 @@
/*
Mojo Additions
Version 0.3e
Created: 2018
Author: Jonathan Wise
License: MIT
Description: Functions I use often, that probably should have been part of Mojo.
They either fix bugs in Mojo or make it easier to use.
*/
this.Additions = function() {}
Mojo.Log.info("#### Mojo Additions added");
Additions.ShowDialogBox = function(title, message) {
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
this.controller.showAlertDialog({
onChoose: function(value) {},
title: title,
message: message,
choices: [{ label: 'OK', value: 'OK' }],
allowHTMLMessage: true
});
}
}
Additions.DisableWidget = function(widgetName, disabledValue) {
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
var thisWidgetSetup = this.controller.getWidgetSetup(widgetName);
var thisWidgetModel = thisWidgetSetup.model;
thisWidgetModel.disabled = disabledValue;
this.controller.setWidgetModel(widgetName, thisWidgetModel);
this.controller.modelChanged(thisWidgetModel);
}
}
Additions.SetPickerWidgetValue = function(widgetName, newvalue) {
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
var thisWidgetModel = this.controller.getWidgetSetup(widgetName).model;
thisWidgetModel.value = newvalue;
this.controller.setWidgetModel(widgetName, thisWidgetModel);
}
}
Additions.SetWidgetLabel = function(widgetName, newvalue) {
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
var thisWidgetModel = this.controller.getWidgetSetup(widgetName).model;
thisWidgetModel.label = newvalue;
this.controller.setWidgetModel(widgetName, thisWidgetModel);
}
}
Additions.SetToggleState = function(widgetName, toggledValue) {
var stageController = Mojo.Controller.getAppController().getStageController("");
if (stageController) {
this.controller = stageController.activeScene();
var thisWidgetSetup = this.controller.getWidgetSetup(widgetName);
var thisWidgetModel = thisWidgetSetup.model;
thisWidgetModel.value = toggledValue;
this.controller.setWidgetModel(widgetName, thisWidgetModel);
//There appears to be a bug in Mojo that means a toggle button doesn't reflect its model state during instantiation
// This work-around fixes it.
var children = document.getElementById(widgetName).querySelectorAll('*');
for (var i = 0; i < children.length; i++) {
if (children[i].className.indexOf("toggle-button") != -1) {
children[i].className = "toggle-button " + thisWidgetModel.value;
}
if (children[i].tagName == "SPAN") {
if (thisWidgetModel.value.toString().toLowerCase() == "true")
children[i].innerHTML = "on";
else
children[i].innerHTML = "off";
}
}
}
}
Additions.FindAncestorWithIdPart = function(currElement, namePartToSearch, expectedIndex, levelsToClimb) {
var parentList = "";
if (typeof levelsToClimb === undefined || levelsToClimb == null)
levelsToClimb = 10;
var foundElement;
for (var i = 0; i < levelsToClimb; i++) {
var parentElement = currElement.parentElement;
if (parentElement != null && parentElement.id != null && parentElement.id != "") {
parentList += parentElement.id + ",";
if (parentElement.id.indexOf(namePartToSearch) == expectedIndex) {
foundElement = parentElement;
}
} else {
parentList += "null,";
}
currElement = parentElement;
}
Mojo.Log.info("Discovered ancestor ids: " + parentList);
return foundElement;
}
Additions.EnumerateObject = function(objectToEnumerate) {
for (var key in objectToEnumerate) {
Mojo.Log.info("=== prop:" + key + ": " + objectToEnumerate[key]);
if (objectToEnumerate.hasOwnProperty(key)) {
var obj = objectToEnumerate[key];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
Mojo.Log.info("...... sub: " + prop + " = " + obj[prop])
}
}
}
}
}
+109
View File
@@ -0,0 +1,109 @@
/*
App Model
Version 0.4
Created: 2018
Author: Jonathan Wise
License: MIT
Description: Common functions for webOS apps, particularly for managing persisted options in cookies
*/
var AppModel = function() {
//Define your default scene (needed for settings management)
this.DefaultScene = "main";
//Define your app-wide, non-persisted settings here
this.UpdateCheckDone = false;
this.LaunchQuery = "";
this.ServiceEndpointBase = "http://chat.webosarchive.com/";
this.ClientId = secrets.clientid;
//Define your app preferences (to be saved by OS)
this.AppSettingsCurrent = null;
this.AppSettingsDefaults = {
ForegroundUpdate: 10000,
UseClientAPIKey: false,
UseCustomEndpoint: false,
ClientAPIKey: "",
EndpointURL: "",
SenderName: "webOS User",
FirstRun: true
};
}
//You probably don't need to change the below functions since they all work against the Cookie defaults you defined above.
// LoadSettings: call when your app starts, or you want to load previously persisted options.
// SaveSettings: call any time you want to persist an option.
// ResetSettings: call if you want to forget stored settings and return to defaults. Your default scene will be popped and re-pushed.
AppModel.prototype.LoadSettings = function(safe) {
this.AppSettingsCurrent = this.AppSettingsDefaults;
var loadSuccess = false;
var settingsCookie = new Mojo.Model.Cookie("settings");
try {
appSettings = settingsCookie.get();
if ((typeof appSettings == "undefined" || appSettings == null) || (safe && !this.checkSettingsValid(appSettings))) {
Mojo.Log.warn("** Using first run default settings");
} else {
Mojo.Log.info("** Using cookie settings!");
this.loadCookieIntoCurrent(appSettings);
loadSuccess = true;
}
} catch (ex) {
settingsCookie.put(null);
Mojo.Log.error("** Settings cookie were corrupt and have been purged!");
Mojo.Log.error(ex);
}
return loadSuccess;
}
AppModel.prototype.loadCookieIntoCurrent = function(cookieSettings) {
this.AppSettingsCurrent = cookieSettings;
}
AppModel.prototype.checkSettingsValid = function(loadedSettings) {
var retValue = true;
for (var key in this.AppSettingsDefaults) {
if (typeof loadedSettings[key] === undefined || loadedSettings[key] == null) {
Mojo.Log.warn("** An expected saved setting, " + key + ", was null or undefined.");
retValue = false;
}
if (typeof loadedSettings[key] !== typeof this.AppSettingsDefaults[key]) {
Mojo.Log.warn("** A saved setting, " + key + ", was of type " + typeof(loadedSettings[key]) + " but expected type " + typeof(this.AppSettingsDefaults[key]));
retValue = false;
}
if (typeof this.AppSettingsDefaults[key] === "string" && this.AppSettingsDefaults[key].indexOf(this.BaseDateString) != -1 && loadedSettings[key].indexOf(this.BaseDateString)) {
Mojo.Log.warn("** A saved setting could not be compared to an expected date value.");
retValue = false;
}
if (typeof this.AppSettingsDefaults[key] === "string" && (this.AppSettingsDefaults[key] == "false" || this.AppSettingsDefaults[key] == "true")) {
if (loadedSettings[key] != "false" && loadedSettings[key] != "true") {
Mojo.Log.warn("** A saved setting did not have the expected boolean value.");
retValue = false;
}
}
}
return retValue;
}
AppModel.prototype.SaveSettings = function() {
var settingsCookie = new Mojo.Model.Cookie("settings");
Mojo.Log.info("Saving settings as: " + JSON.stringify(appModel.AppSettingsCurrent));
Mojo.Log.info("Default settings at Save time are: " + JSON.stringify(appModel.AppSettingsDefaults));
settingsCookie.put(appModel.AppSettingsCurrent);
}
AppModel.prototype.ResetSettings = function() {
Mojo.Log.info("resetting settings");
//Tell main scene to drop settings
this.AppSettingsCurrent = this.AppSettingsDefaults;
this.SaveSettings();
Mojo.Log.info("settings have been reset");
var stageController = Mojo.Controller.getAppController().getActiveStageController();
stageController.popScene(this.DefaultScene);
Mojo.Log.info("closed default scene");
//Restart main scene
stageController.pushScene(this.DefaultScene);
Mojo.Log.info("re-opened default scene");
}
+462
View File
@@ -0,0 +1,462 @@
/*
System Model
Version 0.9
Created: 2020
Author: Jonathan Wise
License: MIT
Description: A generic and re-usable model for accessing webOS system features more easily
Privileged functions can only be called if your App ID starts with com.palm.webos
*/
var SystemModel = function() {
};
//Create a named System Alarm using relative time ("in")
SystemModel.prototype.SetSystemAlarmRelative = function(alarmName, alarmTime) {
this.wakeupRequest = new Mojo.Service.Request("palm://com.palm.power/timeout", {
method: "set",
parameters: {
"key": Mojo.Controller.appInfo.id + "-" + alarmName,
"in": alarmTime,
"wakeup": true,
"uri": "palm://com.palm.applicationManager/open",
"params": {
"id": Mojo.Controller.appInfo.id,
"params": { "action": alarmName }
}
},
onSuccess: function(response) {
Mojo.Log.info("Alarm Set Success", JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Alarm Set Failure, " + alarmTime + ":",
JSON.stringify(response), response.errorText);
}
});
return true;
}
//Create a named System Alarm using absolute time ("at")
SystemModel.prototype.SetSystemAlarmAbsolute = function(alarmName, alarmTime) {
this.wakeupRequest = new Mojo.Service.Request("palm://com.palm.power/timeout", {
method: "set",
parameters: {
"key": Mojo.Controller.appInfo.id + "-" + alarmName,
"at": alarmTime,
"wakeup": true,
"uri": "palm://com.palm.applicationManager/open",
"params": {
"id": Mojo.Controller.appInfo.id,
"params": { "action": alarmName }
}
},
onSuccess: function(response) {
Mojo.Log.info("Alarm Set Success", JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Alarm Set Failure, " + alarmTime + ":",
JSON.stringify(response), response.errorText);
}
});
return true;
}
//Remove a named System alarm
SystemModel.prototype.ClearSystemAlarm = function(alarmName) {
Mojo.Log.warn("Clearing alarm: " + alarmName);
this.wakeupRequest = new Mojo.Service.Request("palm://com.palm.power/timeout", {
method: "clear",
parameters: { "key": Mojo.Controller.appInfo.id + "-" + alarmName },
onSuccess: function(response) {
Mojo.Log.info("Alarm Clear Success", JSON.stringify(response));
success = true;
},
onFailure: function(response) {
Mojo.Log.error("Alarm Clear Failure",
JSON.stringify(response), response.errorText);
success = false;
}
});
return true;
}
//Play a pre-defined system sound
SystemModel.prototype.PlaySound = function(soundName) {
Mojo.Log.info("Playing sound: " + soundName);
this.soundRequest = new Mojo.Service.Request("palm://com.palm.audio/systemsounds", {
method: "playFeedback",
parameters: {
name: soundName
},
onSuccess: function() { success = true; },
onFailure: function() { success = false; }
});
}
//Allow the display to sleep
SystemModel.prototype.AllowDisplaySleep = function(stageController) {
if (!stageController)
stageController = Mojo.Controller.getAppController().getActiveStageController();
//Tell the System it doesn't have to stay awake any more
Mojo.Log.info("Allowing display sleep");
stageController.setWindowProperties({
blockScreenTimeout: false
});
}
//Prevent the display from sleeping
SystemModel.prototype.PreventDisplaySleep = function(stageController) {
if (!stageController)
stageController = Mojo.Controller.getAppController().getActiveStageController();
//Ask the System to stay awake while timer is running
Mojo.Log.info("Preventing display sleep");
stageController.setWindowProperties({
blockScreenTimeout: true
});
}
//Show a notification window in its own small stage
// Launches with sound: pass true for default, false for no sound, or pass the path to a specific sound file
SystemModel.prototype.ShowNotificationStage = function(stageName, sceneName, heightToUse, sound, vibrate) {
Mojo.Log.info("Showing notification stage.");
//Determine what sound to use
var soundToUse;
if (!sound)
soundToUse = "assets/silent.mp3";
else if (sound == true || sound == "")
soundToUse = "/media/internal/ringtones/Dulcimer (short).mp3"
else
soundToUse = sound;
if (vibrate != null)
this.Vibrate(vibrate);
var stageCallBack = function(stageController) {
stageController.pushScene({ name: stageName, sceneTemplate: sceneName });
}
Mojo.Controller.getAppController().createStageWithCallback({
name: stageName,
lightweight: true,
height: heightToUse,
sound: soundToUse,
clickableWhenLocked: true
}, stageCallBack, 'popupalert');
}
//Vibrate the device
SystemModel.prototype.Vibrate = function(vibrate) {
var success = true;
Mojo.Log.info("Vibrating device.");
if (!Number(vibrate)) {
if (vibrate == true)
vibeMax = 1;
else
vibeMax = 0;
} else
vibeMax = Number(vibrate);
if (vibeMax > 0)
vibeInterval = setInterval(doVibrate, 500);
return success;
}
//Launch an app
SystemModel.prototype.LaunchApp = function(appName, params) {
if (!params)
params = {};
if (!params.id)
params.id = appName;
this.launchRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "open",
parameters: params,
onSuccess: function(response) {
Mojo.Log.info("App Launch Success", appName, JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Alarm Launch Failure", appName, JSON.stringify(response));
}
});
return true;
}
//Helper Functions
var vibeInterval;
var vibeCount = 0;
var vibeMax = 5;
doVibrate = function() {
vibeCount++;
new Mojo.Service.Request("palm://com.palm.vibrate", {
method: "vibrate",
parameters: { "period": 500, "duration": 1000 }
});
if (vibeCount >= vibeMax) {
clearInterval(vibeInterval);
vibeCount = 0;
}
}
//Privileged functions
/* These functions can only be called with apps that have com.palm.webos as the start of their App Id */
//Set the System Volume to a given level
SystemModel.prototype.SetSystemVolume = function(newVolume) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
this.service_identifier = 'palm://com.palm.audio/system';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'setVolume',
parameters: { volume: newVolume },
onSuccess: function(response) { Mojo.Log.info("System volume set to " + newVolume); },
onFailure: function(response) { Mojo.Log.warn("System volume not set!", JSON.stringify(response)); }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Get the current System Volume to a callback
SystemModel.prototype.GetSystemVolume = function(callback) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
this.service_identifier = 'palm://com.palm.audio/system';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'getVolume',
onSuccess: callback,
onFailure: callback
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the Ringtone Volume to a given level
SystemModel.prototype.SetRingtoneVolume = function(newVolume) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
this.service_identifier = 'palm://com.palm.audio/ringtone';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'setVolume',
parameters: { volume: newVolume },
onSuccess: function(response) { Mojo.Log.info("Ringtone volume set to " + newVolume); },
onFailure: function(response) { Mojo.Log.warn("Ringtone volume not set!", JSON.stringify(response)); }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Get the current Ringtone Volume to a callback
SystemModel.prototype.GetRingtoneVolume = function(callback) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
this.service_identifier = 'palm://com.palm.audio/ringtone';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'getVolume',
onSuccess: callback,
onFailure: callback
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the System Brightness to a given level
SystemModel.prototype.SetSystemBrightness = function(newBrightness) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
this.service_identifier = 'palm://com.palm.display/control';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'setProperty',
parameters: { maximumBrightness: newBrightness },
onSuccess: function(response) { Mojo.Log.info("Screen brightness set to " + newBrightness); },
onFailure: function(response) { Mojo.Log.warn("Screen brightess not set!", JSON.stringify(response)); }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Get the System Brightness
SystemModel.prototype.GetSystemBrightness = function(callback) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Getting display state");
new Mojo.Service.Request("palm://com.palm.display/control", {
method: "getProperty",
parameters: { properties: ['timeout', 'maximumBrightness'] },
onSuccess: callback,
onFailure: callback
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Get the state of the display ("undefined", "dimmed", "off" or "on") to a callback
SystemModel.prototype.GetDisplayState = function(callBack) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Getting display state");
new Mojo.Service.Request("palm://com.palm.display/control", {
method: "status",
parameters: {},
onSuccess: callBack,
onFailure: callBack
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the state of the display ("unlocked", "dimmed", "off" or "on")
SystemModel.prototype.SetDisplayState = function(state) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Setting display state to " + state);
new Mojo.Service.Request("palm://com.palm.display/control", {
method: "setState",
parameters: { "state": state },
onSuccess: function(response) {
Mojo.Log.info("Display set success: ", JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Display set error: ", JSON.stringify(response), response.errorText);
}
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the Notifications-When-Locked state
SystemModel.prototype.setShowNotificationsWhenLocked = function(value) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Setting Notifications When Locked to " + value);
this.service_identifier = 'palm://com.palm.systemservice';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'setPreferences',
parameters: { showAlertsWhenLocked: value }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the LED Notification state
SystemModel.prototype.setLEDLightNotifications = function(value) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Setting LED Notifications to " + value);
this.service_identifier = 'palm://com.palm.systemservice';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'setPreferences',
parameters: { BlinkNotifications: value }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the WAN state
SystemModel.prototype.setWANEnabled = function(value) {
var state = value ? 'off' : 'on';
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Setting WAN State to " + value);
this.service_identifier = 'palm://com.palm.wan/';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'set',
parameters: { disablewan: state },
onSuccess: function(response) { Mojo.Log.info("WAN state set to " + value); },
onFailure: function(response) { Mojo.Log.warn("WAN state not set!", JSON.stringify(response)); }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the WIFI state
SystemModel.prototype.setWifiEnabled = function(value) {
var state = value ? 'enabled' : 'disabled';
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Setting WIFI State to " + state);
this.service_identifier = 'palm://com.palm.wifi';
var request = new Mojo.Service.Request(this.service_identifier, {
method: 'setstate',
parameters: { 'state': state },
onSuccess: function(response) { Mojo.Log.info("Wifi state set to " + state); },
onFailure: function(response) { Mojo.Log.warn("Wifi state not set!", JSON.stringify(response)); }
});
return request;
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
//Set the Bluetooth radio state
SystemModel.prototype.SetBluetoothEnabled = function(value) {
//var state = value ? 'enabled':'disabled';
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Setting Bluetooth State to " + value);
if (value == true)
this.bluetoothControlService("palm://com.palm.btmonitor/monitor/radioon", { visible: true, connectable: true }, null);
else
this.bluetoothControlService("palm://com.palm.btmonitor/monitor/radiooff", null, null);
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
SystemModel.prototype.bluetoothControlService = function(url, params, cb) {
return new Mojo.Service.Request(url, {
onSuccess: cb,
onFailure: cb,
parameters: params,
});
};
SystemModel.prototype.GetRunningApps = function(callBack) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Getting list of running apps.");
this.appRequest = new Mojo.Service.Request("palm://com.palm.applicationManager/running", {
method: "",
parameters: {},
onSuccess: callBack,
onFailure: callBack
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
SystemModel.prototype.KillApp = function(appId) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm.webos") != -1) {
Mojo.Log.info("Killing app id: " + appId);
this.appRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "close",
parameters: { "processId": appId },
onSuccess: function(response) { Mojo.Log.info("App was killed: " + appId); },
onFailure: function(response) { Mojo.Log.warn("App was not killed!", JSON.stringify(response)); }
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm.webos'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
+162
View File
@@ -0,0 +1,162 @@
/*
Updater Model - Mojo
Version 0.2
Created: 2020
Author: Jonathan Wise
License: MIT
Description: A model to check for and get updates from App Museum II web service.
Does not require App Museum to be installed, but does require internet access, and Preware to do the actual install.
Source: Find the latest version of this library and clean samples of how to use it on GitHub:
https://github.com/codepoet80/webos-catalog-frontend/tree/main/Examples
*/
var UpdaterModel = function() {
this.updateURL = "http://appcatalog.webosarchive.com/WebService/getLatestVersionInfo.php?app=";
this.lastUpdateResponse = null;
};
/* "Public" Updater functions */
//Check App Museum II web service to see if there are any updates
UpdaterModel.prototype.CheckForUpdate = function(appName, callback) {
var currVersion = this.getVersionObject(Mojo.Controller.appInfo.version);
Mojo.Log.info("UpdaterModel identified current app " + appName + " version: " + JSON.stringify(currVersion));
// TODO: It would be nice to use the AppID, instead of an arbitrary name, but the performance
// implications are overwhelming since the AppID is not a part of the masterData file.
// We could get that from the app with: Mojo.Controller.appInfo.id
this.updateURL = this.updateURL + encodeURI(appName);
// set scope for xmlhttp anonymous function callback
if (callback)
callBack = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", this.updateURL);
Mojo.Log.info("Updater calling: " + this.updateURL);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
Mojo.Log.info("Museum responded: " + xmlhttp.responseText);
if (xmlhttp.responseText != null && xmlhttp.responseText != "" && xmlhttp.responseText.indexOf("ERROR:") != 1) {
var updateResponse = JSON.parse(xmlhttp.responseText);
if (updateResponse.version != null) {
var museumVersion = this.getVersionObject(updateResponse.version);
//Mojo.Log.info("Museum version: " + JSON.stringify(museumVersion));
if (this.isVersionHigher(currVersion, museumVersion)) {
Mojo.Log.warn("UpdaterModel found an update in webOS App Museum II!");
updateResponse.updateFound = true;
} else {
Mojo.Log.info("UpdaterModel did not find an update in webOS App Museum II!");
updateResponse = false;
}
}
this.lastUpdateResponse = updateResponse;
} else {
Mojo.Log.info("UpdaterModel: No useable response from App Museum II update API");
}
//Mojo.Log.info("New update response object: " + JSON.stringify(updateResponse));
if (callback) {
callBack(updateResponse);
}
}
}.bind(this);
}
//You can optionally call this function if you don't want to handle the user interaction related to prompting for an update
// Pass the function to be called back with the user's response
// Optionally pass a message to the user if you don't like the automatically constructed one
UpdaterModel.prototype.PromptUserForUpdate = function(callback, message) {
if (!this.lastUpdateResponse) {
Mojo.Log.warn("UpdaterModel: Not prompting user for update when no update has been discovered.");
} else {
if (!message)
message = "An update for " + Mojo.Controller.appInfo.title + " was found in App Museum II. Do you want to update now?! <br>" + this.lastUpdateResponse.versionNote + "<br>Do you want to update now?";
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
this.controller.showAlertDialog({
onChoose: function(value) {
if (value) {
Mojo.Log.info("User requested update now");
} else {
Mojo.Log.info("User deferred update.");
}
if (callback)
callback(value);
},
allowHTMLMessage: true,
title: $L("Update Available!"),
message: $L(message),
choices: [
{ label: $L('Update Now'), value: true, type: 'affirmative' },
{ label: $L("Later"), value: false, type: 'negative' }
]
});
}
}
}
//Ask Preware to actually install the update...
UpdaterModel.prototype.InstallUpdate = function(callBack) {
if (!this.lastUpdateResponse) {
Mojo.Log.warn("UpdaterModel: Not performing update when no update has been discovered.");
} else {
var app = this.lastUpdateResponse.downloadURI;
Mojo.Log.info("Asking PreWare to perform update to " + app);
//Ask webOS to launch the video player with the new url
this.prewareRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "open",
parameters: {
"id": "org.webosinternals.preware",
params: { type: "install", file: app }
},
onSuccess: function(response) {
Mojo.Log.info("Preware launch success", JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Preware launch failure, " + videoURL + ":",
JSON.stringify(response), response.errorText);
}
});
return true;
}
}
/* "Private" helper functions */
//Turn a version string into an object with three independent number values
UpdaterModel.prototype.getVersionObject = function(versionNum) {
versionNumParts = versionNum.split(".");
if (versionNumParts.length <= 2 || versionNumParts > 3) {
Mojo.Log.error("UpdaterModel: An invalid version number was passed, webOS version numbers are #.#.#");
return false;
} else {
var versionObject = {
majorVersion: versionNumParts[0] * 1,
minorVersion: versionNumParts[1] * 1,
buildVersion: versionNumParts[2] * 1
}
return versionObject;
}
}
//Given a current version and a version to compare, return true or false if the compare version is newer
UpdaterModel.prototype.isVersionHigher = function(currVersion, compareVersion) {
if (!currVersion || !compareVersion) {
Mojo.Log.error("UpdaterModel: Pass the versions to compare. If the second version is higher than the first, this function will return true");
} else {
if (compareVersion.majorVersion > currVersion.majorVersion)
return true;
if (compareVersion.majorVersion == currVersion.majorVersion && compareVersion.minorVersion > currVersion.minorVersion)
return true;
if (compareVersion.majorVersion == currVersion.majorVersion && compareVersion.minorVersion == currVersion.minorVersion && compareVersion.buildVersion > currVersion.buildVersion)
return true;
return false;
}
}
+6
View File
@@ -0,0 +1,6 @@
<div class="palm-row" x-mojo-tap-highlight='momentary'>
<div class="palm-row-wrapper">
<div class="chat-log" style="color:#{color}"><b>#{sender}:</b> #{message}</div>
<div class="chat-time">#{timestamp}</div>
</div>
</div>
+3
View File
@@ -0,0 +1,3 @@
<div class="palm-list">
<div> #{-listElements}</div>
</div>
+22
View File
@@ -0,0 +1,22 @@
<div class="palm-hasheader" style="margin-bottom:60px">
<div id="watchViewTitle" class="palm-header center">
webOS SimpleChat
</div>
</div>
<div id="showChatList" name="showChatList" style="display:block;">
<div class="palm-group">
<div class="palm-group-title"><span x-mojo-loc=''>Chat Log</span></div>
<div x-mojo-element="Scroller" id="chatScroller" class="scrollerClass" name="chatScroller">
<div id="chatList" x-mojo-element="List"></div>
</div>
</div>
</div>
<div class="palm-group" style="height:36px;">
<div class="palm-group-title"><span x-mojo-loc=''>Send Message</span></div>
<div x-mojo-element="TextField" id="txtMessage" class="textFieldClass" name="txtMessage" style="margin-right:12px"></div>
<div id="divSpinnerContainer" style="width: 30px;margin-left: auto;margin-right: auto; align-items: center; margin-top:-25px;">
<div x-mojo-element="Spinner" class="spinnerClass" id="workingSpinner" name="workingSpinner"></div>
</div>
</div>
@@ -0,0 +1,64 @@
<div class="palm-hasheader" style="margin-bottom:60px">
<div id="watchViewTitle" class="palm-header center" onclick="Mojo.Controller.stageController.popScene();">
Preferences
</div>
</div>
<div class="palm-group">
<div class="palm-group-title">
<span x-mojo-loc=''>Basic</span>
</div>
<div class="palm-list">
<div class="palm-row first">
<div class="palm-row-wrapper">
<div id="listForegroundUpdate" x-mojo-element="ListSelector" title="ForegroundUpdate" class="right"></div>
</div>
</div>
<div class="palm-row last">
<div class="palm-row-wrapper">
<div id="listBackgroundUpdate" x-mojo-element="ListSelector" title="BackgroundUpdate" class="right"></div>
</div>
</div>
</div>
</div>
<div class="palm-group">
<div class="palm-group-title">
<span x-mojo-loc=''>Self Hosting</span>
</div>
<div class="palm-list">
<div class="palm-row first">
<div class="palm-row-wrapper">
<div x-mojo-element="ToggleButton" id="toggleClientAPI" title="UseClientAPIKey"></div>
<div class="title" style="margin-left: -4px; float:left;"><span class="label">Custom Client Id</span></div>
</div>
</div>
<div class="palm-row">
<div class="palm-row-wrapper" style="margin:14px 22px 4px 12px">
<div id="txtClientAPI" name="txtClientAPI" x-mojo-element="TextField" title="ClientAPIKey"></div>
</div>
</div>
<div class="palm-row first">
<div class="palm-row-wrapper">
<div x-mojo-element="ToggleButton" id="toggleCustomEndPoint" title="UseCustomEndpoint"></div>
<div class="title" style="margin-left: -4px; float:left;"><span class="label">Custom Endpoint URL</span></div>
</div>
</div>
<div class="palm-row last">
<div class="palm-row-wrapper" style="margin:14px 22px 4px 12px">
<div id="txtEndpointURL" name="txtEndpointURL" x-mojo-element="TextField" title="EndpointURL"></div>
</div>
</div>
</div>
</div>
<div class="explanation-text">For more information on self-hosting, please visit <a href="https://github.com/codepoet80/simplechat-service">https://github.com/codepoet80/simplechat-service</a></div>
<div x-mojo-element="Button" id="btnOK" name="btnOK"></div>
+22
View File
@@ -0,0 +1,22 @@
<div class="palm-dialog-content">
<div id="hue-title" class="palm-dialog-title">
Getting Acquainted
</div>
<div class="palm-dialog-seperator" style="height: 8px;"></div>
<div class="palm-body-text">
SimpleChat is a lightweight message board for webOS. It keeps a log of recent messages, and allows you to post public messages.<br> How do you want to identify yourself?
</div>
<div class="textfield-group" x-mojo-focus-highlight="true" id="divUserInfo" style="display:block; margin-left: 15px; margin-right: 15px;">
<div class="title">
<div id="txtUserName" name="txtUserName" x-mojo-element="TextField" title="senderName"></div>
</div>
</div>
<div class="palm-dialog-seperator" style="height: 18px;"></div>
<div class="palm-dialog-buttons">
<div x-mojo-element="Button" id="goButton"></div>
<div x-mojo-element="Button" id="cancelButton"></div>
</div>
</div>
+11
View File
@@ -0,0 +1,11 @@
{
"id": "com.jonandnic.simplechat",
"version": "0.1.1",
"vendor": "Jon Wise",
"type": "web",
"main": "index.html",
"title": "SimpleChat",
"icon": "icon.png",
"uiRevision": "2",
"theme": "light"
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

+7
View File
@@ -0,0 +1,7 @@
{
"logLevel": 99,
"debuggingEnabled": true,
"timingEnabled": false,
"logEvents": false,
"escapeHTMLInTemplates" : true
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='height=device-height'>
<title>Simple Chat</title>
<script src="/usr/palm/frameworks/mojo/mojo.js" type="text/javascript" x-mojo-version="1"></script>
<!-- load my additions -->
<script src="app/com.jonandnic.mojo.additions.js" type="text/javascript"></script>
<!-- application stylesheet should come in after the one loaded by the framework -->
<link href="stylesheets/simplechat.css" media="screen" rel="stylesheet" type="text/css">
</head>
<body>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
var secrets = {
'clientid': 'SecretSharedWithServer'
};
+20
View File
@@ -0,0 +1,20 @@
[
{ "source": "app/assistants/app-assistant.js" },
{ "source": "app/assistants/stage-assistant.js" },
{
"scenes": "main",
"source": "app/assistants/main-assistant.js"
},
{
"scenes": "preferences",
"source": "app/assistants/preferences-assistant.js"
},
{
"source": "app/assistants/user-assistant.js",
"comment": "since this will be a dialog, not a scene, we do not include the 'scenes' member (or we get errors)"
},
{ "source": "app/models/system-model.js" },
{ "source": "app/models/updater-model.js" },
{ "source": "app/models/app-model.js" },
{ "source": "secrets.js" }
]
+24
View File
@@ -0,0 +1,24 @@
/* TODO: put your application-specific CSS here */
.explanation-text {
margin-top: -5px;
padding: 14px;
font-size: 14px;
color: #000;
}
.chat-log {
margin-top: 8px;
}
.chat-time {
margin-top: 6px;
font-size: 12px;
font-style: italic;
color: dimgray;
padding-bottom: 14px;
}
.textFieldClass input {
padding: 1px;
}