Skip to content

Commit

Permalink
Merge pull request #446 from athombv/develop
Browse files Browse the repository at this point in the history
mdim
  • Loading branch information
jeroenwienk authored Sep 18, 2024
2 parents bf565fc + 7953349 commit bae79ef
Show file tree
Hide file tree
Showing 11 changed files with 1,019 additions and 129 deletions.
28 changes: 28 additions & 0 deletions assets/templates/app/widgets/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

module.exports = {
async getSomething({ homey, query }) {
// you can access query parameters like "/?foo=bar" through `query.foo`

// you can access the App instance through homey.app
// const result = await homey.app.getSomething();
// return result;

// perform other logic like mapping result data

return 'Hello from App';
},

async addSomething({ homey, body }) {
// access the post body and perform some action on it.
return homey.app.addSomething(body);
},

async updateSomething({ homey, params, body }) {
return homey.app.setSomething(body);
},

async deleteSomething({ homey, params }) {
return homey.app.deleteSomething(params.id);
},
};
Binary file added assets/templates/app/widgets/preview-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/templates/app/widgets/preview-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions assets/templates/app/widgets/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<html>
<head>
<style>
/* Example of a custom CSS class. */
.custom-image-class {
margin: var(--homey-su-3) auto var(--homey-su-5);
}
</style>
</head>

<body class="homey-widget">
<img src="homey-logo.png" alt="Homey logo" class="custom-image-class" />
<p class="homey-text-regular homey-text-align-center">Edit public/index.html and hit refresh.</p>

<script type="text/javascript">
function onHomeyReady(Homey) {
Homey.ready({ height: 188 });

// View the settings the user provided if your widget has settings.
console.log('Widget settings:', Homey.getSettings());

// Fetch something from your app.
Homey.api('GET', '/', {})
.then((result) => {
console.log(result);
})
.catch(console.error);
}
</script>
</body>
</html>
9 changes: 9 additions & 0 deletions bin/cmds/app/widget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

// exports.desc = 'Widget related commands';
exports.builder = yargs => {
return yargs
.commandDir('widget')
.demandCommand()
.help();
};
16 changes: 16 additions & 0 deletions bin/cmds/app/widget/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

const Log = require('../../../../lib/Log');
const App = require('../../../../lib/App');

exports.desc = 'Create a new Widget';
exports.handler = async yargs => {
try {
const app = new App(yargs.path);
await app.createWidget();
process.exit(0);
} catch (err) {
Log.error(err);
process.exit(1);
}
};
159 changes: 156 additions & 3 deletions lib/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const zlib = require('zlib');
const http = require('http');
const stream = require('stream');
const { promisify } = require('util');
const sharp = require('sharp');

const { AthomAppsAPI, HomeyAPIV2 } = require('homey-api');
const { getAppLocales } = require('homey-lib');
Expand Down Expand Up @@ -444,6 +445,21 @@ class App {
});

// Proxy local assets

const middlewares = {};

// During development with docker we get the widget public files from the source folder so that
// the app does not have to be restarted when the widget files change. Making a change and
// reloading the widget should fetch the new file.
serverApp.use('/widgets/:widgetId/public', (req, res, next) => {
const widgetId = req.params.widgetId;
if (!middlewares[widgetId]) {
const widgetPath = path.join(this.path, 'widgets', widgetId, 'public');
middlewares[widgetId] = express.static(widgetPath);
}

return middlewares[widgetId](req, res, next);
});
serverApp.use('/', express.static(this._homeyBuildPath));

// Start the HTTP Server
Expand Down Expand Up @@ -499,15 +515,14 @@ class App {
.on('getFile', ({ path }, callback) => {
Promise.resolve().then(async () => {
const res = await fetch(`http://localhost:${serverPort}${path}`);
const { status } = res;
const headers = {
'Content-Type': res.headers.get('Content-Type') || undefined,
'X-Homey-Hash': res.headers.get('X-Homey-Hash') || undefined,
};
const body = await res.buffer();

return {
status,
status: res.status,
headers,
body,
};
Expand Down Expand Up @@ -876,6 +891,44 @@ $ sudo systemctl restart docker

await fse.copy(fullSrc, fullDest);
}

const appJson = await fs.promises.readFile(path.join(this.path, 'app.json')).then(data => {
return JSON.parse(data);
});

if (appJson.widgets) {
for (const [widgetId] of Object.entries(appJson.widgets)) {
const previewLightPath = path.join(this.path, 'widgets', widgetId, 'preview-light.png');
const previewDarkPath = path.join(this.path, 'widgets', widgetId, 'preview-dark.png');

// eslint-disable-next-line no-useless-catch
try {
await fs.promises.access(previewLightPath);
await fs.promises.access(previewDarkPath);

const imageLight = sharp(previewLightPath);
const imageDark = sharp(previewDarkPath);

await fs.promises.mkdir(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__'), { recursive: true });
await Promise.all([
fs.promises.copyFile(previewLightPath, path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', 'preview-light.png')),
imageLight.resize(128, 128).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageLight.resize(192, 192).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageLight.resize(256, 256).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageLight.resize(384, 384).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageLight.resize(512, 512).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
fs.promises.copyFile(previewDarkPath, path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', 'preview-dark.png')),
imageDark.resize(128, 128).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageDark.resize(192, 192).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageDark.resize(256, 256).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageDark.resize(384, 384).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
imageDark.resize(512, 512).toFile(path.join(this._homeyBuildPath, 'widgets', widgetId, '__assets__', '[email protected]')),
]);
} catch (error) {
throw error;
}
}
}
}

async _copyAppProductionDependencies() {
Expand Down Expand Up @@ -1305,6 +1358,8 @@ $ sudo systemctl restart docker
Log(colors.white(`\nVisit https://tools.developer.homey.app/apps/app/${appId}/build/${buildId} to publish your app.`));
} catch (error) {
for (const undo of Object.values(undos)) {
if (undo === null) continue;

await undo().catch(err => {
Log.error(err);
});
Expand Down Expand Up @@ -1409,7 +1464,7 @@ $ sudo systemctl restart docker
'tsconfig.json',
'env.json',
'*.compose.json',
'node_modules/*',
'node_modules',
];
// Add a "file" containing our default ignore rules for dotfiles, env.json and node_modules
walker.onReadIgnoreFile(DEFAULT_IGNORE_RULES_FILE, ignoreRules.join('\r\n'), () => { });
Expand Down Expand Up @@ -2624,6 +2679,104 @@ $ sudo systemctl restart docker
Log.success(`Flow created in \`${flowPath}\``);
}

async createWidget() {
if (App.hasHomeyCompose({ appPath: this.path }) === false) {
// Note: this checks that we are in a valid homey app folder
App.getManifest({ appPath: this.path });

if (await this._askComposeMigration()) {
await this.migrateToCompose();
} else {
throw new Error('This command requires Homey compose, run `homey app compose` to migrate!');
}
}

const { widgetName } = await inquirer.prompt([
{
type: 'input',
name: 'widgetName',
message: 'What is your Widgets\'s Name?',
validate: input => input.length > 0,
},
]);

const {
widgetId,
} = await inquirer.prompt([
{
type: 'input',
name: 'widgetId',
message: 'What is your Widgets\'s ID?',
default: () => {
let name = widgetName;
name = name.toLowerCase();
name = name.replace(/ /g, '-');
name = name.replace(INVALID_CHARACTERS, '');
return name;
},
validate: input => {
if (input.match(INVALID_CHARACTERS)) {
throw new Error('Invalid characters: only use letters, numbers, minus (-) and underscore (_)');
}

if (fs.existsSync(path.join(this.path, 'widgets', input))) {
throw new Error('Widget directory already exists!');
}

return true;
},
},
]);

const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Seems good?',
},
]);

if (!confirm) return;

const widgetPath = path.join(this.path, 'widgets', widgetId);
await fse.ensureDir(widgetPath);

const widgetJson = {
name: { en: widgetName },
settings: [],
api: {
getSomething: {
method: 'GET',
path: '/',
},
addSomething: {
method: 'POST',
path: '/',
},
updateSomething: {
method: 'PUT',
path: '/:id',
},
deleteSomething: {
method: 'DELETE',
path: '/:id',
},
},
};

await writeFileAsync(path.join(widgetPath, 'widget.compose.json'), JSON.stringify(widgetJson, false, 2));

const templatePath = path.join(__dirname, '..', 'assets', 'templates', 'app', 'widgets');
await fse.ensureDir(path.join(widgetPath, 'public'));
await copyFileAsync(path.join(templatePath, 'public/index.html'), path.join(widgetPath, 'public/index.html'));
await copyFileAsync(path.join(templatePath, 'public/homey-logo.png'), path.join(widgetPath, 'public/homey-logo.png'));
await copyFileAsync(path.join(templatePath, 'api.js'), path.join(widgetPath, 'api.js'));
await copyFileAsync(path.join(templatePath, 'preview-dark.png'), path.join(widgetPath, 'preview-dark.png'));
await copyFileAsync(path.join(templatePath, 'preview-light.png'), path.join(widgetPath, 'preview-light.png'));

Log.success(`Widget created in \`${widgetPath}\``);
}

async createDiscoveryStrategy() {
if (App.hasHomeyCompose({ appPath: this.path }) === false) {
// Note: this checks that we are in a valid homey app folder
Expand Down
Loading

0 comments on commit bae79ef

Please sign in to comment.