From: Jaroslav Kysela Date: Thu, 3 Dec 2015 18:16:49 +0000 (+0100) Subject: wizard: initial (demo) work X-Git-Tag: v4.2.1~1378 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ec6abbbc8376ddde477cff4ab95929f282ed3d79;p=thirdparty%2Ftvheadend.git wizard: initial (demo) work --- diff --git a/Makefile b/Makefile index 802ab0712..36596545a 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,8 @@ SRCS-1 = \ src/intlconv.c \ src/profile.c \ src/bouquet.c \ - src/lock.c + src/lock.c \ + src/wizard.c SRCS = $(SRCS-1) I18N-C = $(SRCS-1) @@ -249,7 +250,8 @@ SRCS-2 = \ src/api/api_bouquet.c \ src/api/api_language.c \ src/api/api_satip.c \ - src/api/api_timeshift.c + src/api/api_timeshift.c \ + src/api/api_wizard.c SRCS-2 += \ src/parsers/parsers.c \ diff --git a/Makefile.webui b/Makefile.webui index af06a4bcc..5db29ec96 100644 --- a/Makefile.webui +++ b/Makefile.webui @@ -125,6 +125,7 @@ JAVASCRIPT += $(ROOTPATH)/app/epggrab.js JAVASCRIPT += $(ROOTPATH)/app/config.js JAVASCRIPT += $(ROOTPATH)/app/tvhlog.js JAVASCRIPT += $(ROOTPATH)/app/status.js +JAVASCRIPT += $(ROOTPATH)/app/wizard.js JAVASCRIPT += $(ROOTPATH)/tv.js JAVASCRIPT += $(ROOTPATH)/app/servicemapper.js diff --git a/src/api.c b/src/api.c index 6db216a24..41b4ba07b 100644 --- a/src/api.c +++ b/src/api.c @@ -140,6 +140,7 @@ void api_init ( void ) api_language_init(); api_satip_server_init(); api_timeshift_init(); + api_wizard_init(); } void api_done ( void ) diff --git a/src/api.h b/src/api.h index 3ab69921b..b5365e0e4 100644 --- a/src/api.h +++ b/src/api.h @@ -80,6 +80,7 @@ void api_profile_init ( void ); void api_language_init ( void ); void api_satip_server_init ( void ); void api_timeshift_init ( void ); +void api_wizard_init ( void ); /* * IDnode diff --git a/src/api/api_wizard.c b/src/api/api_wizard.c new file mode 100644 index 000000000..e77bf740a --- /dev/null +++ b/src/api/api_wizard.c @@ -0,0 +1,82 @@ +/* + * API - Wizard interface + * + * Copyright (C) 2015 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; withm even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "channels.h" +#include "access.h" +#include "api.h" +#include "config.h" +#include "wizard.h" + +static int +wizard_idnode_load_simple + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + int r; + wizard_build_fcn_t fcn = opaque; + wizard_page_t *page = fcn(); + r = api_idnode_load_simple(perm, &page->idnode, op, args, resp); + page->free(page); + return r; +} + +static int +wizard_idnode_save_simple + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + int r; + wizard_build_fcn_t fcn = opaque; + wizard_page_t *page = fcn(); + r = api_idnode_save_simple(perm, &page->idnode, op, args, resp); + page->free(page); + return r; +} + +static int +wizard_cancel + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + pthread_mutex_lock(&global_lock); + free(config.wizard); + config.wizard = strdup(""); + config_save(); + pthread_mutex_unlock(&global_lock); + return 0; +} + +void +api_wizard_init ( void ) +{ + static api_hook_t ah[] = { + { "wizard/hello/load", ACCESS_ADMIN, wizard_idnode_load_simple, wizard_hello }, + { "wizard/hello/save", ACCESS_ADMIN, wizard_idnode_save_simple, wizard_hello }, + { "wizard/network/load", ACCESS_ADMIN, wizard_idnode_load_simple, wizard_network }, + { "wizard/network/save", ACCESS_ADMIN, wizard_idnode_save_simple, wizard_network }, + { "wizard/input/load", ACCESS_ADMIN, wizard_idnode_load_simple, wizard_input }, + { "wizard/input/save", ACCESS_ADMIN, wizard_idnode_save_simple, wizard_input }, + { "wizard/status/load", ACCESS_ADMIN, wizard_idnode_load_simple, wizard_status }, + { "wizard/status/save", ACCESS_ADMIN, wizard_idnode_save_simple, wizard_status }, + { "wizard/mapping/load", ACCESS_ADMIN, wizard_idnode_load_simple, wizard_mapping }, + { "wizard/mapping/save", ACCESS_ADMIN, wizard_idnode_save_simple, wizard_mapping }, + { "wizard/cancel", ACCESS_ADMIN, wizard_cancel, NULL }, + { NULL }, + }; + + api_register_all(ah); +} diff --git a/src/config.c b/src/config.c index 62065529a..4c42afa6a 100644 --- a/src/config.c +++ b/src/config.c @@ -1626,6 +1626,7 @@ config_boot ( const char *path, gid_t gid, uid_t uid ) memset(&config, 0, sizeof(config)); config.idnode.in_class = &config_class; + config.wizard = strdup("hello"); config.info_area = strdup("login,storage,time"); config.cookie_expires = 7; config.dscp = -1; @@ -1732,6 +1733,7 @@ config_init ( int backup ) void config_done ( void ) { /* note: tvhlog is inactive !!! */ + free(config.wizard); free(config.full_version); free(config.server_name); free(config.language); @@ -2110,6 +2112,13 @@ const idclass_t config_class = { .opts = PO_ADVANCED, .group = 6, }, + { + .type = PT_STR, + .id = "wizard", + .name = "Wizard level", /* untranslated */ + .off = offsetof(config_t, wizard), + .opts = PO_NOUI, + }, {} } }; diff --git a/src/config.h b/src/config.h index 57798bfb6..ec733914b 100644 --- a/src/config.h +++ b/src/config.h @@ -32,6 +32,7 @@ typedef struct config { uint32_t version; int uilevel; int uilevel_nochange; + char *wizard; char *full_version; char *server_name; char *language; diff --git a/src/webui/comet.c b/src/webui/comet.c index f2dfc5cc1..b35308ef1 100644 --- a/src/webui/comet.c +++ b/src/webui/comet.c @@ -150,6 +150,7 @@ comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb) const char *username = hc->hc_access ? (hc->hc_access->aa_username ?: "") : ""; int64_t bfree, btotal; int dvr = !http_access_verify(hc, ACCESS_RECORDER); + int admin = !http_access_verify(hc, ACCESS_ADMIN); const char *s; htsmsg_add_str(m, "notificationClass", "accessUpdate"); @@ -170,7 +171,7 @@ comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb) if (hc->hc_peer_ipstr) htsmsg_add_str(m, "address", hc->hc_peer_ipstr); htsmsg_add_u32(m, "dvr", dvr); - htsmsg_add_u32(m, "admin", !http_access_verify(hc, ACCESS_ADMIN)); + htsmsg_add_u32(m, "admin", admin); htsmsg_add_s64(m, "time", time(NULL)); @@ -185,6 +186,9 @@ comet_access_update(http_connection_t *hc, comet_mailbox_t *cmb) htsmsg_add_s64(m, "totaldiskspace", btotal); } + if (admin && config.wizard) + htsmsg_add_str(m, "wizard", config.wizard); + if(cmb->cmb_messages == NULL) cmb->cmb_messages = htsmsg_create_list(); htsmsg_add_msg(cmb->cmb_messages, NULL, m); diff --git a/src/webui/static/app/ext.css b/src/webui/static/app/ext.css index 4a078cfce..0de9fa4f7 100644 --- a/src/webui/static/app/ext.css +++ b/src/webui/static/app/ext.css @@ -168,6 +168,10 @@ background-image: url(../icons/find.png) !important; } +.wizard { + background-image: url(../icons/wand.png) !important; +} + .uilevel { background-image: url(../icons/application_form.png) !important; } @@ -584,6 +588,15 @@ background-image: url(../icons/fetch_images.png) !important; } +.previous { + background-image: url(../icons/arrow_left.png) !important; +} + +.next { + background-image: url(../icons/arrow_right.png) !important; +} + + .x-linked { display: inline-block; background-image: url(../icons/linked.gif) !important; diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index 07c8d76b0..444cd0c24 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -831,7 +831,7 @@ tvheadend.idnode_editor_form = function(uilevel, d, meta, panel, conf) var p = d[i]; if (conf.uuids && p.rdonly) continue; - if (conf.noui) + if (p.noui) continue; var f = tvheadend.idnode_editor_field(p, conf); if (!f) @@ -1025,20 +1025,22 @@ tvheadend.idnode_editor = function(_uilevel, item, conf) var cancelBtn = new Ext.Button({ text: _('Cancel'), iconCls: 'cancel', - handler: conf.cancel + handler: function() { + conf.cancel(conf); + } }); buttons.push(cancelBtn); } var saveBtn = new Ext.Button({ - text: _('Save'), - iconCls: 'save', + text: conf.saveText || _('Save'), + iconCls: conf.saveIconCls || 'save', handler: function() { if (panel.getForm().isDirty()) { var node = panel.getForm().getFieldValues(); node.uuid = conf.uuids ? conf.uuids : item.uuid; tvheadend.Ajax({ - url: 'api/idnode/save', + url: conf.saveURL || 'api/idnode/save', params: { node: Ext.encode(node) }, @@ -1051,6 +1053,8 @@ tvheadend.idnode_editor = function(_uilevel, item, conf) if (conf.win) conf.win.close(); } + if (conf.postsave) + conf.postsave(conf); } }); buttons.push(saveBtn); @@ -1064,7 +1068,7 @@ tvheadend.idnode_editor = function(_uilevel, item, conf) var node = panel.getForm().getFieldValues(); node.uuid = conf.uuids ? conf.uuids : item.uuid; tvheadend.Ajax({ - url: 'api/idnode/save', + url: conf.saveURL || 'api/idnode/save', params: { node: Ext.encode(node) }, @@ -1078,8 +1082,11 @@ tvheadend.idnode_editor = function(_uilevel, item, conf) buttons.push(applyBtn); } + var uilevelBtn = null; - if (!tvheadend.uilevel_nochange && (!conf.uilevel || conf.uilevel !== 'expert')) { + if (!tvheadend.uilevel_nochange && + (!conf.uilevel || conf.uilevel !== 'expert') && + !conf.noUIlevel) { uilevelBtn = tvheadend.idnode_uilevel_menu(uilevel, function(l) { uilevel = l; var values = panel.getForm().getFieldValues(); @@ -1100,6 +1107,9 @@ tvheadend.idnode_editor = function(_uilevel, item, conf) buttons.push(uilevelBtn ? '-' : '->'); buttons.push(helpBtn); } + + if (conf.buildbtn) + conf.buildbtn(conf, buttons); } panel = new Ext.form.FormPanel({ @@ -1122,6 +1132,29 @@ tvheadend.idnode_editor = function(_uilevel, item, conf) return panel; }; +/* + * + */ +tvheadend.idnode_editor_win = function(_uilevel, item, conf) +{ + var p = tvheadend.idnode_editor(_uilevel, item, conf); + var width = p.fixedWidth; + var w = new Ext.ux.Window({ + title: conf.winTitle, + iconCls: 'edit', + layout: 'fit', + autoWidth: width ? false : true, + autoHeight: true, + autoScroll: true, + plain: true, + items: p + }); + conf.win = w; + if (width) + w.setWidth(width); + w.show(); + return w; +} /* * IDnode creation dialog @@ -1669,28 +1702,13 @@ tvheadend.idnode_grid = function(panel, conf) } }; if (uuids.length > 1) { - var title = String.format(_('Edit {0} ({1} entries)'), - conf.titleS, uuids.length); + c.winTitle = String.format(_('Edit {0} ({1} entries)'), + conf.titleS, uuids.length); c.uuids = uuids; } else { - var title = String.format(_('Edit {0}'), conf.titleS); + c.winTitle = String.format(_('Edit {0}'), conf.titleS); } - var p = tvheadend.idnode_editor(uilevel, d[0], c); - var width = p.fixedWidth; - w = new Ext.ux.Window({ - title: title, - iconCls: 'edit', - layout: 'fit', - autoWidth: width ? false : true, - autoHeight: true, - autoScroll: true, - plain: true, - items: p - }); - if (width) - w.setWidth(width); - c.win = w; - w.show(); + tvheadend.idnode_editor_win(uilevel, d[0], c); } }); } diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index a9e45c750..92427793a 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -5,6 +5,7 @@ tvheadend.admin = false; tvheadend.dialog = null; tvheadend.uilevel = 'expert'; tvheadend.uilevel_nochange = false; +tvheadend.wizard = null; tvheadend.cookieProvider = new Ext.state.CookieProvider({ // 7 days from now @@ -586,6 +587,9 @@ function accessUpdate(o) { } tvheadend.rootTabPanel.doLayout(); + + if (o.wizard) + tvheadend.wizard_start(o.wizard); } /** diff --git a/src/webui/static/app/wizard.js b/src/webui/static/app/wizard.js new file mode 100644 index 000000000..4f663af28 --- /dev/null +++ b/src/webui/static/app/wizard.js @@ -0,0 +1,92 @@ +/* + * Wizard + */ + +tvheadend.wizard_start = function(page) { + + var w = null; + + function cancel(conf) { + tvheadend.Ajax({ + url: 'api/wizard/cancel' + }); + tvheadend.wizard = null; + if (conf.win) + conf.win.close(); + } + + function getparam(data, prefix) { + var m = data.params; + var l = prefix.length; + for (var i = 0; i < m.length; i++) { + var id = m[i].id; + if (id.substring(0, l) === prefix) + return id.substring(l, 32); + } + return null; + } + + function newpage(conf, prefix) { + var p = getparam(conf.fullData, prefix); + if (p) + tvheadend.wizard_start(p); + else + Ext.MessageBox.alert(String.format(_('Wizard - page "{0}" not found'), prefix)); + } + + function buildbtn(conf, buttons) { + if (!getparam(conf.fullData, 'page_prev_')) + return; + var prevBtn = new Ext.Button({ + text: _('Previous'), + iconCls: 'previous', + handler: function() { + newpage(conf, 'page_prev_'); + }, + }); + buttons.splice(0, 0, prevBtn); + } + + function build(d) { + d = json_decode(d); + var m = d[0]; + var last = getparam(m, 'page_next_') === null; + tvheadend.idnode_editor_win('basic', m, { + fullData: m, + url: 'api/wizard/' + page, + winTitle: m.caption, + iconCls: 'wizard', + comet: m.events, + noApply: true, + noUIlevel: true, + postsave: function(conf) { + if (!last) + newpage(conf, 'page_next_'); + else + cancel(conf); + }, + buildbtn: buildbtn, + labelWidth: 250, + saveText: last ? _('Finish') : _('Save & Next'), + saveIconCls: last ? 'exit' : 'next', + cancel: cancel, + uilevel: 'expert', + help: function() { + new tvheadend.help(_('Wizard - initial configuration and tutorial'), 'config_wizard.html'); + } + }); + } + + tvheadend.wizard = page; + tvheadend.Ajax({ + url: 'api/wizard/' + page + '/load', + params: { + meta: 1 + }, + success: build, + failure: function(response, options) { + Ext.MessageBox.alert(_('Unable to obtain wizard page!'), response.statusText); + } + }); + +}; diff --git a/src/webui/static/icons/arrow_left.png b/src/webui/static/icons/arrow_left.png new file mode 120000 index 000000000..494b4cd9c --- /dev/null +++ b/src/webui/static/icons/arrow_left.png @@ -0,0 +1 @@ +../../../../vendor/famfamsilk/arrow_left.png \ No newline at end of file diff --git a/src/webui/static/icons/arrow_right.png b/src/webui/static/icons/arrow_right.png new file mode 120000 index 000000000..1a46dc0a0 --- /dev/null +++ b/src/webui/static/icons/arrow_right.png @@ -0,0 +1 @@ +../../../../vendor/famfamsilk/arrow_right.png \ No newline at end of file diff --git a/src/wizard.c b/src/wizard.c new file mode 100644 index 000000000..66366dc4e --- /dev/null +++ b/src/wizard.c @@ -0,0 +1,245 @@ +/* + * tvheadend, Wizard + * Copyright (C) 2015 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "access.h" +#include "settings.h" +#include "wizard.h" + +/* + * + */ + +static const void *empty_get(void *o) +{ + prop_sbuf[0] = '\0'; + return &prop_sbuf_ptr; +} + +#define SPECIAL_PROP(idval) { \ + .type = PT_STR, \ + .id = idval, \ + .name = "", \ + .get = empty_get, \ + .opts = PO_RDONLY | PO_NOUI \ +} + +#define PREV_BUTTON(page) SPECIAL_PROP("page_prev_" STRINGIFY(page)) +#define NEXT_BUTTON(page) SPECIAL_PROP("page_next_" STRINGIFY(page)) +#define LAST_BUTTON() SPECIAL_PROP("page_last") + +/* + * + */ + +static void page_free(wizard_page_t *page) +{ + free((char *)page->idnode.in_class); + free(page); +} + +static wizard_page_t *page_init(const char *class_name, const char *caption) +{ + wizard_page_t *page = calloc(1, sizeof(*page)); + idclass_t *ic = calloc(1, sizeof(*ic)); + page->idnode.in_class = ic; + ic->ic_caption = caption; + ic->ic_class = ic->ic_event = class_name; + ic->ic_perm_def = ACCESS_ADMIN; + page->free = page_free; + return page; +} + +/* + * Hello + */ + +static const void *hello_get_network(void *o) +{ + strcpy(prop_sbuf, "Test123"); + return &prop_sbuf_ptr; +} + +static int hello_set_network(void *o, const void *v) +{ + return 0; +} + +wizard_page_t *wizard_hello(void) +{ + static const property_t props[] = { + { + .type = PT_STR, + .id = "network", + .name = N_("Network (like 192.168.1.0/24)"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "username", + .name = N_("Username"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "password", + .name = N_("Password"), + .get = hello_get_network, + .set = hello_set_network, + }, + NEXT_BUTTON(network), + {} + }; + wizard_page_t *page = page_init("wizard_hello", N_("Welcome - Tvheadend - your TV streaming server and video recorder")); + idclass_t *ic = (idclass_t *)page->idnode.in_class; + ic->ic_properties = props; + return page; +} + +/* + * Network settings + */ + +wizard_page_t *wizard_network(void) +{ + static const property_t props[] = { + { + .type = PT_STR, + .id = "network1", + .name = N_("Network 1"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "network2", + .name = N_("Network 2"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "network3", + .name = N_("Network 3"), + .get = hello_get_network, + .set = hello_set_network, + }, + PREV_BUTTON(hello), + NEXT_BUTTON(input), + {} + }; + wizard_page_t *page = page_init("wizard_network", N_("Network settings")); + idclass_t *ic = (idclass_t *)page->idnode.in_class; + ic->ic_properties = props; + return page; +} + +/* + * Input settings + */ + +wizard_page_t *wizard_input(void) +{ + static const property_t props[] = { + { + .type = PT_STR, + .id = "input1", + .name = N_("Input 1 network"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "input2", + .name = N_("Input 2 network"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "input3", + .name = N_("Input 3 network"), + .get = hello_get_network, + .set = hello_set_network, + }, + PREV_BUTTON(network), + NEXT_BUTTON(status), + {} + }; + wizard_page_t *page = page_init("wizard_input", N_("Input / tuner settings")); + idclass_t *ic = (idclass_t *)page->idnode.in_class; + ic->ic_properties = props; + return page; +} + +/* + * Status + */ + +wizard_page_t *wizard_status(void) +{ + static const property_t props[] = { + { + .type = PT_STR, + .id = "muxes", + .name = N_("Found muxes"), + .get = hello_get_network, + .set = hello_set_network, + }, + { + .type = PT_STR, + .id = "services", + .name = N_("Found services"), + .get = hello_get_network, + .set = hello_set_network, + }, + PREV_BUTTON(input), + NEXT_BUTTON(mapping), + {} + }; + wizard_page_t *page = page_init("wizard_status", N_("Scan status")); + idclass_t *ic = (idclass_t *)page->idnode.in_class; + ic->ic_properties = props; + return page; +} + +/* + * Service Mapping + */ + +wizard_page_t *wizard_mapping(void) +{ + static const property_t props[] = { + { + .type = PT_STR, + .id = "pnetwork", + .name = N_("Select network"), + .get = hello_get_network, + .set = hello_set_network, + }, + PREV_BUTTON(status), + LAST_BUTTON(), + {} + }; + wizard_page_t *page = page_init("wizard_service_map", N_("Service mapping")); + idclass_t *ic = (idclass_t *)page->idnode.in_class; + ic->ic_properties = props; + return page; +} diff --git a/src/wizard.h b/src/wizard.h new file mode 100644 index 000000000..fc83a5e6a --- /dev/null +++ b/src/wizard.h @@ -0,0 +1,38 @@ +/* + * tvheadend, Wizard + * Copyright (C) 2015 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_WIZARD_H__ +#define __TVH_WIZARD_H__ + +#include "tvheadend.h" +#include "idnode.h" + +typedef struct wizard_page { + idnode_t idnode; + void (*free)(struct wizard_page *); +} wizard_page_t; + +typedef wizard_page_t *(*wizard_build_fcn_t)(void); + +wizard_page_t *wizard_hello(void); +wizard_page_t *wizard_network(void); +wizard_page_t *wizard_input(void); +wizard_page_t *wizard_status(void); +wizard_page_t *wizard_mapping(void); + +#endif /* __TVH_WIZARD_H__ */