example coded by GPT 5.5 with Codex
This commit is contained in:
@@ -0,0 +1,695 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var STORAGE_KEY = "webos-reference-lab-state";
|
||||
var focusables = [];
|
||||
var focusedIndex = 0;
|
||||
var logItems = [];
|
||||
var networkSubscribed = false;
|
||||
var lastNetworkStatus = null;
|
||||
var canvas = document.getElementById("motionCanvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
var sceneNames = ["Aurora Signal", "Ember Orbit", "Orchid Pulse"];
|
||||
var themes = ["theme-aurora", "theme-ember", "theme-orchid"];
|
||||
var state = readStorage();
|
||||
var lastFrame = 0;
|
||||
var requestFrame = window.requestAnimationFrame || function (callback) {
|
||||
return window.setTimeout(function () {
|
||||
callback(new Date().getTime());
|
||||
}, 16);
|
||||
};
|
||||
|
||||
function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function readStorage() {
|
||||
var fallback = {
|
||||
scene: 0,
|
||||
theme: 0,
|
||||
opens: 0,
|
||||
lastAction: "Fresh install"
|
||||
};
|
||||
|
||||
try {
|
||||
var raw = window.localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
var parsed = JSON.parse(raw);
|
||||
fallback.scene = Number(parsed.scene) || 0;
|
||||
fallback.theme = Number(parsed.theme) || 0;
|
||||
fallback.opens = Number(parsed.opens) || 0;
|
||||
fallback.lastAction = parsed.lastAction || fallback.lastAction;
|
||||
return fallback;
|
||||
} catch (error) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function writeStorage() {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
} catch (error) {
|
||||
addLog("Storage", "localStorage unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
function setText(id, value) {
|
||||
var node = $(id);
|
||||
if (node) {
|
||||
node.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
function compactJson(value) {
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch (error) {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function setDetail(title, payload) {
|
||||
setText("detailTitle", title);
|
||||
setText("detailJson", compactJson(payload));
|
||||
}
|
||||
|
||||
function addLog(label, message) {
|
||||
var stamp = new Date();
|
||||
var time = two(stamp.getHours()) + ":" + two(stamp.getMinutes()) + ":" + two(stamp.getSeconds());
|
||||
logItems.unshift({
|
||||
label: label,
|
||||
message: message,
|
||||
time: time
|
||||
});
|
||||
logItems = logItems.slice(0, 4);
|
||||
renderLog();
|
||||
}
|
||||
|
||||
function renderLog() {
|
||||
var log = $("eventLog");
|
||||
var html = "";
|
||||
var i;
|
||||
|
||||
for (i = 0; i < logItems.length; i += 1) {
|
||||
html += '<div class="log-line"><strong>' + escapeHtml(logItems[i].time + " " + logItems[i].label) + '</strong> ' + escapeHtml(logItems[i].message) + "</div>";
|
||||
}
|
||||
|
||||
log.innerHTML = html;
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function two(value) {
|
||||
value = Number(value) || 0;
|
||||
return value < 10 ? "0" + value : String(value);
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
var i;
|
||||
|
||||
for (i = 0; i < themes.length; i += 1) {
|
||||
document.body.classList.remove(themes[i]);
|
||||
}
|
||||
|
||||
state.scene = state.scene % sceneNames.length;
|
||||
state.theme = state.theme % themes.length;
|
||||
document.body.classList.add(themes[state.theme]);
|
||||
setText("sceneLabel", sceneNames[state.scene]);
|
||||
}
|
||||
|
||||
function initFocus() {
|
||||
focusables = Array.prototype.slice.call(document.querySelectorAll("[data-focusable]"));
|
||||
|
||||
focusables.forEach(function (node, index) {
|
||||
node.addEventListener("focus", function () {
|
||||
focusCard(index);
|
||||
});
|
||||
|
||||
node.addEventListener("click", function () {
|
||||
focusCard(index);
|
||||
runAction(node.getAttribute("data-action"));
|
||||
});
|
||||
});
|
||||
|
||||
focusCard(0);
|
||||
}
|
||||
|
||||
function focusCard(index) {
|
||||
var i;
|
||||
|
||||
if (!focusables.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
index = focusables.length - 1;
|
||||
}
|
||||
|
||||
if (index >= focusables.length) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
focusedIndex = index;
|
||||
for (i = 0; i < focusables.length; i += 1) {
|
||||
focusables[i].classList.toggle("is-focused", i === focusedIndex);
|
||||
}
|
||||
|
||||
if (document.activeElement !== focusables[focusedIndex]) {
|
||||
focusables[focusedIndex].focus();
|
||||
}
|
||||
}
|
||||
|
||||
function moveFocus(direction) {
|
||||
var current = focusables[focusedIndex];
|
||||
var currentRect;
|
||||
var currentCenter;
|
||||
var bestIndex = focusedIndex;
|
||||
var bestScore = Infinity;
|
||||
var i;
|
||||
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentRect = current.getBoundingClientRect();
|
||||
currentCenter = centerOf(currentRect);
|
||||
|
||||
for (i = 0; i < focusables.length; i += 1) {
|
||||
var candidate = focusables[i];
|
||||
var rect = candidate.getBoundingClientRect();
|
||||
var center = centerOf(rect);
|
||||
var dx = center.x - currentCenter.x;
|
||||
var dy = center.y - currentCenter.y;
|
||||
var primary;
|
||||
var secondary;
|
||||
var score;
|
||||
|
||||
if (i === focusedIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (direction === "left" && dx >= -4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (direction === "right" && dx <= 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (direction === "up" && dy >= -4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (direction === "down" && dy <= 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
primary = direction === "left" || direction === "right" ? Math.abs(dx) : Math.abs(dy);
|
||||
secondary = direction === "left" || direction === "right" ? Math.abs(dy) : Math.abs(dx);
|
||||
score = primary * 8 + secondary;
|
||||
|
||||
if (score < bestScore) {
|
||||
bestScore = score;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex !== focusedIndex) {
|
||||
focusCard(bestIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function centerOf(rect) {
|
||||
return {
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
function handleKeydown(event) {
|
||||
var code = event.keyCode || event.which;
|
||||
var keyName = event.key || String(code);
|
||||
|
||||
setText("keyValue", keyName);
|
||||
|
||||
if (code === 37) {
|
||||
event.preventDefault();
|
||||
moveFocus("left");
|
||||
} else if (code === 38) {
|
||||
event.preventDefault();
|
||||
moveFocus("up");
|
||||
} else if (code === 39) {
|
||||
event.preventDefault();
|
||||
moveFocus("right");
|
||||
} else if (code === 40) {
|
||||
event.preventDefault();
|
||||
moveFocus("down");
|
||||
} else if (code === 13) {
|
||||
event.preventDefault();
|
||||
runAction(focusables[focusedIndex].getAttribute("data-action"));
|
||||
} else if (code === 461 || code === 10009 || code === 27) {
|
||||
event.preventDefault();
|
||||
handleBack();
|
||||
} else if (code === 403) {
|
||||
cycleScene();
|
||||
} else if (code === 404) {
|
||||
refreshAll();
|
||||
} else if (code === 405) {
|
||||
runAction("storage");
|
||||
} else if (code === 406) {
|
||||
logItems = [];
|
||||
renderLog();
|
||||
}
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
addLog("Back", "platformBack requested");
|
||||
|
||||
if (window.webOS && typeof window.webOS.platformBack === "function") {
|
||||
window.webOS.platformBack();
|
||||
}
|
||||
}
|
||||
|
||||
function runAction(action) {
|
||||
state.lastAction = action;
|
||||
|
||||
if (action === "clock") {
|
||||
getClock();
|
||||
} else if (action === "device") {
|
||||
getDeviceInfo();
|
||||
} else if (action === "network") {
|
||||
getNetworkStatus();
|
||||
} else if (action === "launch") {
|
||||
launchBrowser();
|
||||
} else if (action === "storage") {
|
||||
saveLocalState();
|
||||
} else if (action === "motion") {
|
||||
cycleScene();
|
||||
}
|
||||
|
||||
writeStorage();
|
||||
}
|
||||
|
||||
function refreshAll() {
|
||||
loadAppInfo();
|
||||
loadLaunchParams();
|
||||
getClock();
|
||||
getDeviceInfo();
|
||||
getNetworkStatus();
|
||||
getLGUDID();
|
||||
addLog("Refresh", "runtime probes requested");
|
||||
}
|
||||
|
||||
function loadAppInfo() {
|
||||
var fallback = {
|
||||
id: window.webOS && window.webOS.fetchAppId ? window.webOS.fetchAppId() : "browser.preview",
|
||||
title: "webOS TV Reference Lab",
|
||||
root: window.webOS && window.webOS.fetchAppRootPath ? window.webOS.fetchAppRootPath() : window.location.href
|
||||
};
|
||||
|
||||
if (window.webOS && typeof window.webOS.fetchAppInfo === "function") {
|
||||
window.webOS.fetchAppInfo(function (info) {
|
||||
var payload = info || fallback;
|
||||
payload.root = fallback.root;
|
||||
setText("appTitle", payload.title || payload.id || "App");
|
||||
setDetail("App Info", payload);
|
||||
addLog("App", "appinfo.json loaded");
|
||||
});
|
||||
} else {
|
||||
setText("appTitle", fallback.title);
|
||||
setDetail("App Info", fallback);
|
||||
}
|
||||
|
||||
setText("platformValue", platformLabel());
|
||||
}
|
||||
|
||||
function platformLabel() {
|
||||
var platform = window.webOS && window.webOS.platform ? window.webOS.platform : {};
|
||||
var labels = [];
|
||||
var key;
|
||||
|
||||
for (key in platform) {
|
||||
if (platform.hasOwnProperty(key) && platform[key]) {
|
||||
labels.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.PalmSystem && window.PalmSystem.deviceInfo) {
|
||||
labels.push("PalmSystem");
|
||||
}
|
||||
|
||||
return labels.length ? labels.join(", ") : "Browser";
|
||||
}
|
||||
|
||||
function loadLaunchParams() {
|
||||
var params = {};
|
||||
|
||||
if (window.webOSDev && typeof window.webOSDev.launchParams === "function") {
|
||||
params = window.webOSDev.launchParams();
|
||||
}
|
||||
|
||||
if (!params || Object.keys(params).length === 0) {
|
||||
params = {
|
||||
mode: "preview",
|
||||
scene: sceneNames[state.scene],
|
||||
opens: state.opens
|
||||
};
|
||||
}
|
||||
|
||||
setText("launchParams", compactJson(params));
|
||||
}
|
||||
|
||||
function getClock() {
|
||||
if (window.webOS && window.webOS.service) {
|
||||
window.webOS.service.request("luna://com.palm.systemservice", {
|
||||
method: "clock/getTime",
|
||||
parameters: {},
|
||||
onSuccess: function (args) {
|
||||
var payload = {
|
||||
utc: args.utc,
|
||||
localtime: args.localtime,
|
||||
timezone: args.timezone,
|
||||
source: "LS2"
|
||||
};
|
||||
setText("clockValue", formatNow());
|
||||
setDetail("System Clock", payload);
|
||||
addLog("Clock", "LS2 response received");
|
||||
},
|
||||
onFailure: function (args) {
|
||||
useBrowserClock(args);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
useBrowserClock({
|
||||
errorText: "PalmServiceBridge is not available"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function useBrowserClock(error) {
|
||||
var payload = {
|
||||
local: new Date().toString(),
|
||||
timezone: window.webOS && window.webOS.systemInfo ? window.webOS.systemInfo().timezone : "browser",
|
||||
source: "Browser fallback",
|
||||
error: error && (error.errorText || error.errorCode)
|
||||
};
|
||||
|
||||
setText("clockValue", formatNow());
|
||||
setDetail("System Clock", payload);
|
||||
addLog("Clock", "browser fallback used");
|
||||
}
|
||||
|
||||
function formatNow() {
|
||||
var now = new Date();
|
||||
return two(now.getHours()) + ":" + two(now.getMinutes());
|
||||
}
|
||||
|
||||
function getDeviceInfo() {
|
||||
if (window.webOS && typeof window.webOS.deviceInfo === "function") {
|
||||
window.webOS.deviceInfo(function (info) {
|
||||
var payload = info || {};
|
||||
payload.webOSTVjs = window.webOS.libVersion || "unknown";
|
||||
setDetail("Device Info", payload);
|
||||
addLog("Device", payload.modelName || "device info updated");
|
||||
});
|
||||
} else {
|
||||
setDetail("Device Info", {
|
||||
userAgent: window.navigator.userAgent,
|
||||
screen: window.screen.width + "x" + window.screen.height,
|
||||
webOSTVjs: "not loaded"
|
||||
});
|
||||
addLog("Device", "browser info shown");
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkStatus() {
|
||||
if (networkSubscribed) {
|
||||
if (lastNetworkStatus) {
|
||||
setDetail("Connection", lastNetworkStatus);
|
||||
}
|
||||
addLog("Network", "subscription already active");
|
||||
return;
|
||||
}
|
||||
|
||||
networkSubscribed = true;
|
||||
|
||||
if (window.webOSDev && window.webOSDev.connection) {
|
||||
window.webOSDev.connection.getStatus({
|
||||
subscribe: true,
|
||||
onSuccess: function (status) {
|
||||
var online = status.isInternetConnectionAvailable || status.wired || status.wifi || window.navigator.onLine;
|
||||
lastNetworkStatus = status;
|
||||
setText("networkValue", online ? "Online" : "Offline");
|
||||
setDetail("Connection", status);
|
||||
addLog("Network", "connection status updated");
|
||||
},
|
||||
onFailure: function (error) {
|
||||
networkSubscribed = false;
|
||||
useBrowserNetwork(error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
networkSubscribed = false;
|
||||
useBrowserNetwork({
|
||||
errorText: "webOSDev.connection is not available"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function useBrowserNetwork(error) {
|
||||
var payload = {
|
||||
onLine: window.navigator.onLine,
|
||||
source: "Browser fallback",
|
||||
error: error && (error.errorText || error.errorCode)
|
||||
};
|
||||
|
||||
lastNetworkStatus = payload;
|
||||
setText("networkValue", payload.onLine ? "Online" : "Offline");
|
||||
setDetail("Connection", payload);
|
||||
addLog("Network", "browser status shown");
|
||||
}
|
||||
|
||||
function getLGUDID() {
|
||||
if (window.webOSDev && typeof window.webOSDev.LGUDID === "function") {
|
||||
window.webOSDev.LGUDID({
|
||||
onSuccess: function (payload) {
|
||||
addLog("LGUDID", maskId(payload.id));
|
||||
},
|
||||
onFailure: function () {
|
||||
addLog("LGUDID", "not available in this runtime");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function maskId(id) {
|
||||
if (!id || id.length < 8) {
|
||||
return "hidden";
|
||||
}
|
||||
|
||||
return id.slice(0, 4) + "..." + id.slice(id.length - 4);
|
||||
}
|
||||
|
||||
function launchBrowser() {
|
||||
var target = "https://webostv.developer.lge.com/";
|
||||
var payload = {
|
||||
id: "com.webos.app.browser",
|
||||
target: target
|
||||
};
|
||||
|
||||
setDetail("Launch Browser", payload);
|
||||
|
||||
if (window.webOSDev && typeof window.webOSDev.launch === "function" && window.PalmServiceBridge) {
|
||||
window.webOSDev.launch({
|
||||
id: window.webOSDev.APP.BROWSER,
|
||||
params: {
|
||||
target: target
|
||||
},
|
||||
onSuccess: function () {
|
||||
addLog("Launch", "browser launch requested");
|
||||
},
|
||||
onFailure: function (error) {
|
||||
addLog("Launch", error.errorText || "launch failed");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.open(target, "_blank");
|
||||
addLog("Launch", "window.open fallback");
|
||||
}
|
||||
}
|
||||
|
||||
function saveLocalState() {
|
||||
state.opens += 1;
|
||||
state.theme = (state.theme + 1) % themes.length;
|
||||
state.lastAction = "storage";
|
||||
applyTheme();
|
||||
writeStorage();
|
||||
loadLaunchParams();
|
||||
setDetail("Local State", {
|
||||
key: STORAGE_KEY,
|
||||
value: state
|
||||
});
|
||||
addLog("Storage", "state saved");
|
||||
}
|
||||
|
||||
function cycleScene() {
|
||||
state.scene = (state.scene + 1) % sceneNames.length;
|
||||
state.theme = state.scene;
|
||||
applyTheme();
|
||||
writeStorage();
|
||||
loadLaunchParams();
|
||||
setDetail("Canvas Scene", {
|
||||
scene: sceneNames[state.scene],
|
||||
canvas: canvas.width + "x" + canvas.height,
|
||||
animation: "requestAnimationFrame"
|
||||
});
|
||||
addLog("Canvas", sceneNames[state.scene]);
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var ratio = window.devicePixelRatio || 1;
|
||||
var width = Math.max(320, Math.floor(rect.width * ratio));
|
||||
var height = Math.max(180, Math.floor(rect.height * ratio));
|
||||
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
function drawFrame(timestamp) {
|
||||
paintCanvas(timestamp);
|
||||
requestFrame(drawFrame);
|
||||
}
|
||||
|
||||
function paintCanvas(timestamp) {
|
||||
var width;
|
||||
var height;
|
||||
var i;
|
||||
var t;
|
||||
|
||||
if (!lastFrame) {
|
||||
lastFrame = timestamp;
|
||||
}
|
||||
|
||||
resizeCanvas();
|
||||
width = canvas.width;
|
||||
height = canvas.height;
|
||||
t = timestamp * 0.001;
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
drawBackdrop(width, height, t);
|
||||
|
||||
for (i = 0; i < 9; i += 1) {
|
||||
drawOrb(width, height, t, i);
|
||||
}
|
||||
|
||||
drawBars(width, height, t);
|
||||
lastFrame = timestamp;
|
||||
}
|
||||
|
||||
function drawBackdrop(width, height, t) {
|
||||
var gradient = ctx.createLinearGradient(0, 0, width, height);
|
||||
|
||||
if (state.scene === 1) {
|
||||
gradient.addColorStop(0, "#2a1710");
|
||||
gradient.addColorStop(0.55, "#11161b");
|
||||
gradient.addColorStop(1, "#293227");
|
||||
} else if (state.scene === 2) {
|
||||
gradient.addColorStop(0, "#1b112a");
|
||||
gradient.addColorStop(0.5, "#101620");
|
||||
gradient.addColorStop(1, "#203226");
|
||||
} else {
|
||||
gradient.addColorStop(0, "#0b1720");
|
||||
gradient.addColorStop(0.55, "#11141a");
|
||||
gradient.addColorStop(1, "#1d2b21");
|
||||
}
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.globalAlpha = 0.18;
|
||||
ctx.fillStyle = "#ffffff";
|
||||
|
||||
for (var i = 0; i < 7; i += 1) {
|
||||
var y = (height / 7) * i + Math.sin(t + i) * 18;
|
||||
ctx.fillRect(0, y, width, 1);
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
function drawOrb(width, height, t, index) {
|
||||
var colors = [
|
||||
["#6ce3cf", "#f3cf56"],
|
||||
["#ff7c67", "#83d96c"],
|
||||
["#c49cff", "#6aa9ff"]
|
||||
];
|
||||
var palette = colors[state.scene];
|
||||
var radius = width * (0.035 + (index % 3) * 0.012);
|
||||
var x = width * (0.12 + index * 0.095) + Math.sin(t * (0.7 + index * 0.05)) * width * 0.045;
|
||||
var y = height * (0.48 + Math.sin(t * 0.8 + index) * 0.27);
|
||||
var gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
|
||||
|
||||
gradient.addColorStop(0, palette[index % 2]);
|
||||
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
||||
ctx.globalAlpha = 0.78;
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
function drawBars(width, height, t) {
|
||||
var x = width * 0.64;
|
||||
var y = height * 0.22;
|
||||
var barWidth = width * 0.035;
|
||||
var gap = width * 0.018;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < 7; i += 1) {
|
||||
var level = 0.22 + Math.abs(Math.sin(t * 1.5 + i * 0.7)) * 0.56;
|
||||
ctx.fillStyle = i % 2 ? "rgba(255,255,255,0.26)" : "rgba(155,231,211,0.62)";
|
||||
ctx.fillRect(x + i * (barWidth + gap), y + height * (0.52 - level), barWidth, height * level);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
state.opens += 1;
|
||||
applyTheme();
|
||||
writeStorage();
|
||||
initFocus();
|
||||
loadAppInfo();
|
||||
loadLaunchParams();
|
||||
getClock();
|
||||
getNetworkStatus();
|
||||
getDeviceInfo();
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
window.addEventListener("online", function () {
|
||||
useBrowserNetwork();
|
||||
});
|
||||
window.addEventListener("offline", function () {
|
||||
useBrowserNetwork();
|
||||
});
|
||||
paintCanvas(0);
|
||||
requestFrame(drawFrame);
|
||||
addLog("Ready", "reference lab started");
|
||||
}
|
||||
|
||||
window.onerror = function (message) {
|
||||
addLog("Error", message);
|
||||
};
|
||||
|
||||
init();
|
||||
}());
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"vendor": "My Company",
|
||||
"type": "web",
|
||||
"main": "index.html",
|
||||
"title": "test app",
|
||||
"title": "webOS TV Reference Lab",
|
||||
"icon": "icon.png",
|
||||
"largeIcon": "largeIcon.png"
|
||||
}
|
||||
+99
-42
@@ -8,51 +8,108 @@ SPDX-License-Identifier: Apache-2.0
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>new app</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #202020;
|
||||
}
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: table;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>webOS TV Reference Lab</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<script src="webOSTVjs-1.2.13/webOSTV.js" charset="utf-8"></script>
|
||||
<script src="webOSTVjs-1.2.13/webOSTV-dev.js" charset="utf-8"></script>
|
||||
<script type="text/javascript">
|
||||
//sample code for calling LS2 API
|
||||
webOS.service.request("luna://com.palm.systemservice", {
|
||||
method: "clock/getTime",
|
||||
parameters: {},
|
||||
onSuccess: function (args) {
|
||||
console.log("UTC:", args.utc);
|
||||
},
|
||||
onFailure: function (args) {
|
||||
console.log("Failed to getTime");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<body class="theme-aurora">
|
||||
<main class="app-shell">
|
||||
<header class="topbar">
|
||||
<section class="brand-block">
|
||||
<p class="eyebrow">webOS TV</p>
|
||||
<h1>Reference Lab</h1>
|
||||
</section>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Hello, World!</h1>
|
||||
</div>
|
||||
<section class="status-strip" aria-label="Runtime status">
|
||||
<div class="status-pill">
|
||||
<span>App</span>
|
||||
<strong id="appTitle">Loading</strong>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span>Clock</span>
|
||||
<strong id="clockValue">--:--</strong>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span>Network</span>
|
||||
<strong id="networkValue">Checking</strong>
|
||||
</div>
|
||||
<div class="status-pill">
|
||||
<span>Platform</span>
|
||||
<strong id="platformValue">Detecting</strong>
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="stage-row">
|
||||
<article class="visual-stage" aria-label="Motion canvas">
|
||||
<canvas id="motionCanvas" width="960" height="420"></canvas>
|
||||
<div class="stage-overlay">
|
||||
<p id="sceneLabel">Aurora Signal</p>
|
||||
<strong id="keyValue">Ready</strong>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<aside class="launch-panel">
|
||||
<p class="panel-label">Launch Params</p>
|
||||
<pre id="launchParams">{}</pre>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="workbench">
|
||||
<nav class="feature-grid" id="featureGrid" aria-label="Examples">
|
||||
<button class="feature-card accent-cyan is-focused" type="button" data-action="clock" data-focusable>
|
||||
<span class="card-icon">LS2</span>
|
||||
<strong>System Clock</strong>
|
||||
<small>luna://com.palm.systemservice</small>
|
||||
</button>
|
||||
|
||||
<button class="feature-card accent-green" type="button" data-action="device" data-focusable>
|
||||
<span class="card-icon">TV</span>
|
||||
<strong>Device Info</strong>
|
||||
<small>webOS.deviceInfo</small>
|
||||
</button>
|
||||
|
||||
<button class="feature-card accent-yellow" type="button" data-action="network" data-focusable>
|
||||
<span class="card-icon">NET</span>
|
||||
<strong>Connection</strong>
|
||||
<small>webOSDev.connection</small>
|
||||
</button>
|
||||
|
||||
<button class="feature-card accent-red" type="button" data-action="launch" data-focusable>
|
||||
<span class="card-icon">APP</span>
|
||||
<strong>Launch Browser</strong>
|
||||
<small>webOSDev.launch</small>
|
||||
</button>
|
||||
|
||||
<button class="feature-card accent-purple" type="button" data-action="storage" data-focusable>
|
||||
<span class="card-icon">DB</span>
|
||||
<strong>Local State</strong>
|
||||
<small>localStorage</small>
|
||||
</button>
|
||||
|
||||
<button class="feature-card accent-blue" type="button" data-action="motion" data-focusable>
|
||||
<span class="card-icon">FX</span>
|
||||
<strong>Canvas Scene</strong>
|
||||
<small>requestAnimationFrame</small>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<aside class="detail-panel">
|
||||
<div class="detail-head">
|
||||
<p class="panel-label" id="detailLabel">Selected Example</p>
|
||||
<strong id="detailTitle">System Clock</strong>
|
||||
</div>
|
||||
<pre class="json-view" id="detailJson">{}</pre>
|
||||
<div class="event-log" id="eventLog" aria-live="polite"></div>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="webOSTVjs-1.2.13/webOSTV.js" charset="utf-8"></script>
|
||||
<script src="webOSTVjs-1.2.13/webOSTV-dev.js" charset="utf-8"></script>
|
||||
<script src="app.js" charset="utf-8"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #090b0f;
|
||||
color: #f7f4e8;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
height: 100%;
|
||||
padding: 34px 42px 30px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 26px;
|
||||
justify-content: space-between;
|
||||
min-height: 86px;
|
||||
}
|
||||
|
||||
.brand-block {
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.panel-label {
|
||||
color: #9be7d3;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
margin: 0 0 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 46px;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.status-strip {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-width: 230px;
|
||||
min-width: 140px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.status-pill span {
|
||||
color: #b7bdc7;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-pill strong {
|
||||
color: #ffffff;
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
line-height: 1.2;
|
||||
margin-top: 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stage-row {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
gap: 20px;
|
||||
min-height: 230px;
|
||||
}
|
||||
|
||||
.visual-stage,
|
||||
.launch-panel,
|
||||
.detail-panel {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.visual-stage {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#motionCanvas {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stage-overlay {
|
||||
align-items: flex-start;
|
||||
bottom: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 22px;
|
||||
position: absolute;
|
||||
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.stage-overlay p {
|
||||
color: #c2fff3;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.stage-overlay strong {
|
||||
color: #ffffff;
|
||||
font-size: 36px;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.launch-panel {
|
||||
flex: 0 0 300px;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#launchParams {
|
||||
color: #d7e0ee;
|
||||
font-size: 15px;
|
||||
line-height: 1.45;
|
||||
max-height: 178px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.workbench {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
gap: 20px;
|
||||
min-height: 276px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #171b22;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1 1 30%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 126px;
|
||||
min-width: 220px;
|
||||
outline: none;
|
||||
padding: 18px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
transform: translateZ(0);
|
||||
transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
|
||||
}
|
||||
|
||||
.feature-card:before {
|
||||
background: #6ce3cf;
|
||||
border-radius: 999px;
|
||||
content: "";
|
||||
height: 7px;
|
||||
left: 18px;
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.feature-card.is-focused,
|
||||
.feature-card:focus {
|
||||
background: #222832;
|
||||
border-color: #ffffff;
|
||||
box-shadow: 0 0 0 4px rgba(155, 231, 211, 0.28);
|
||||
transform: scale(1.035);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.feature-card:active {
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
height: 44px;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
width: 58px;
|
||||
}
|
||||
|
||||
.feature-card strong {
|
||||
display: block;
|
||||
font-size: 25px;
|
||||
line-height: 1.08;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.feature-card small {
|
||||
color: #b9c1ce;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.accent-cyan:before {
|
||||
background: #6ce3cf;
|
||||
}
|
||||
|
||||
.accent-green:before {
|
||||
background: #83d96c;
|
||||
}
|
||||
|
||||
.accent-yellow:before {
|
||||
background: #f3cf56;
|
||||
}
|
||||
|
||||
.accent-red:before {
|
||||
background: #ff7c67;
|
||||
}
|
||||
|
||||
.accent-purple:before {
|
||||
background: #c49cff;
|
||||
}
|
||||
|
||||
.accent-blue:before {
|
||||
background: #6aa9ff;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
display: flex;
|
||||
flex: 0 0 390px;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.detail-head {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
flex: 0 0 auto;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-head strong {
|
||||
color: #ffffff;
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.json-view {
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
color: #f1f7ff;
|
||||
flex: 1 1 auto;
|
||||
font-size: 15px;
|
||||
line-height: 1.42;
|
||||
min-height: 96px;
|
||||
overflow: hidden;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.event-log {
|
||||
display: flex;
|
||||
flex: 0 0 92px;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
justify-content: flex-end;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
color: #c7cfda;
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-line strong {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.theme-ember .eyebrow,
|
||||
.theme-ember .panel-label {
|
||||
color: #ffc46b;
|
||||
}
|
||||
|
||||
.theme-ember .feature-card.is-focused,
|
||||
.theme-ember .feature-card:focus {
|
||||
box-shadow: 0 0 0 4px rgba(255, 196, 107, 0.28);
|
||||
}
|
||||
|
||||
.theme-orchid .eyebrow,
|
||||
.theme-orchid .panel-label {
|
||||
color: #dfb3ff;
|
||||
}
|
||||
|
||||
.theme-orchid .feature-card.is-focused,
|
||||
.theme-orchid .feature-card:focus {
|
||||
box-shadow: 0 0 0 4px rgba(223, 179, 255, 0.28);
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.app-shell {
|
||||
overflow-y: auto;
|
||||
padding: 26px;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.topbar,
|
||||
.stage-row,
|
||||
.workbench {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.status-strip {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.launch-panel,
|
||||
.detail-panel {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.visual-stage {
|
||||
min-height: 280px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user