* Channel editor has been reworked a bit. It uses an editorGrid, similar
to how other grids work in Tvheadend. Tags are mapped inline using a list-of-values combobox (http://lovcombo.extjs.eu/)
This commit is contained in:
parent
08466b212e
commit
e8a7044f14
6 changed files with 606 additions and 1250 deletions
7
debian/changelog
vendored
7
debian/changelog
vendored
|
@ -52,7 +52,12 @@ hts-tvheadend (2.3) hts; urgency=low
|
|||
from local to nationwide broadcast (AC3 audio is only present
|
||||
in nationwide broadcast)
|
||||
Ticket #78
|
||||
|
||||
|
||||
* Channel editor has been reworked a bit. It uses an editorGrid, similar
|
||||
to how other grids work in Tvheadend. Tags are mapped inline using
|
||||
a list-of-values combobox (http://lovcombo.extjs.eu/)
|
||||
|
||||
|
||||
hts-tvheadend (2.2) hts; urgency=low
|
||||
|
||||
* Set $HOME so forked processes (XMLTV) will have correct environment
|
||||
|
|
|
@ -270,32 +270,106 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_chlist(http_connection_t *hc, const char *remain, void *opaque)
|
||||
static void
|
||||
extjs_channels_delete(htsmsg_t *in)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
htsmsg_t *out, *array, *c;
|
||||
htsmsg_field_t *f;
|
||||
channel_t *ch;
|
||||
|
||||
out = htsmsg_create_map();
|
||||
TAILQ_FOREACH(f, &in->hm_fields, hmf_link)
|
||||
if(f->hmf_type == HMF_S64 &&
|
||||
(ch = channel_find_by_identifier(f->hmf_s64)) != NULL)
|
||||
channel_delete(ch);
|
||||
}
|
||||
|
||||
array = htsmsg_create_list();
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
extjs_channels_update(htsmsg_t *in)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
channel_t *ch;
|
||||
htsmsg_t *c;
|
||||
uint32_t id;
|
||||
const char *s;
|
||||
|
||||
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
|
||||
c = htsmsg_create_map();
|
||||
htsmsg_add_str(c, "name", ch->ch_name);
|
||||
htsmsg_add_u32(c, "chid", ch->ch_id);
|
||||
htsmsg_add_msg(array, NULL, c);
|
||||
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
|
||||
if((c = htsmsg_get_map_by_field(f)) == NULL ||
|
||||
htsmsg_get_u32(c, "id", &id))
|
||||
continue;
|
||||
|
||||
if((ch = channel_find_by_identifier(id)) == NULL)
|
||||
continue;
|
||||
|
||||
if((s = htsmsg_get_str(c, "name")) != NULL)
|
||||
channel_rename(ch, s);
|
||||
|
||||
if((s = htsmsg_get_str(c, "xmltvsrc")) != NULL)
|
||||
channel_set_xmltv_source(ch, xmltv_channel_find_by_displayname(s));
|
||||
|
||||
if((s = htsmsg_get_str(c, "tags")) != NULL)
|
||||
channel_set_tags_from_list(ch, s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_channels(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
htsmsg_t *array, *c;
|
||||
channel_t *ch;
|
||||
char buf[1024];
|
||||
channel_tag_mapping_t *ctm;
|
||||
const char *op = http_arg_get(&hc->hc_req_args, "op");
|
||||
const char *entries = http_arg_get(&hc->hc_req_args, "entries");
|
||||
|
||||
htsmsg_autodtor(in) =
|
||||
entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
|
||||
|
||||
htsmsg_autodtor(out) = htsmsg_create_map();
|
||||
|
||||
scopedgloballock();
|
||||
|
||||
if(!strcmp(op, "list")) {
|
||||
array = htsmsg_create_list();
|
||||
|
||||
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
|
||||
c = htsmsg_create_map();
|
||||
htsmsg_add_str(c, "name", ch->ch_name);
|
||||
htsmsg_add_u32(c, "chid", ch->ch_id);
|
||||
|
||||
if(ch->ch_xc != NULL)
|
||||
htsmsg_add_str(c, "xmltvsrc", ch->ch_xc->xc_displayname);
|
||||
|
||||
buf[0] = 0;
|
||||
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
|
||||
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
|
||||
"%s%d", strlen(buf) == 0 ? "" : ",",
|
||||
ctm->ctm_tag->ct_identifier);
|
||||
}
|
||||
htsmsg_add_str(c, "tags", buf);
|
||||
|
||||
htsmsg_add_msg(array, NULL, c);
|
||||
}
|
||||
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
} else if(!strcmp(op, "delete") && in != NULL) {
|
||||
extjs_channels_delete(in);
|
||||
|
||||
} else if(!strcmp(op, "update") && in != NULL) {
|
||||
extjs_channels_update(in);
|
||||
|
||||
} else {
|
||||
return 400;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
@ -388,229 +462,6 @@ json_single_record(htsmsg_t *rec, const char *root)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static htsmsg_t *
|
||||
build_transport_msg(th_transport_t *t)
|
||||
{
|
||||
htsmsg_t *r = htsmsg_create_map();
|
||||
th_stream_t *st;
|
||||
const char *n;
|
||||
char video[200];
|
||||
char audio[200];
|
||||
char subtitles[200];
|
||||
char scrambling[200];
|
||||
|
||||
htsmsg_add_u32(r, "enabled", t->tht_enabled);
|
||||
htsmsg_add_str(r, "name", t->tht_svcname);
|
||||
|
||||
htsmsg_add_str(r, "provider", t->tht_provider ?: "");
|
||||
if((n = t->tht_networkname(t)) != NULL)
|
||||
htsmsg_add_str(r, "network", n);
|
||||
htsmsg_add_str(r, "source", t->tht_sourcename(t));
|
||||
|
||||
htsmsg_add_str(r, "status", "");
|
||||
|
||||
video[0] = 0;
|
||||
audio[0] = 0;
|
||||
subtitles[0] = 0;
|
||||
scrambling[0] = 0;
|
||||
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
|
||||
switch(st->st_type) {
|
||||
case SCT_TELETEXT:
|
||||
case SCT_SUBTITLES:
|
||||
case SCT_PAT:
|
||||
case SCT_PMT:
|
||||
break;
|
||||
|
||||
case SCT_MPEG2VIDEO:
|
||||
snprintf(video + strlen(video), sizeof(video) - strlen(video),
|
||||
"%sMPEG-2 (PID:%d", strlen(video) > 0 ? ", " : "",
|
||||
st->st_pid);
|
||||
|
||||
video:
|
||||
if(st->st_frame_duration) {
|
||||
snprintf(video + strlen(video), sizeof(video) - strlen(video),
|
||||
", %d Hz)", 90000 / st->st_frame_duration);
|
||||
} else {
|
||||
snprintf(video + strlen(video), sizeof(video) - strlen(video),
|
||||
")");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SCT_H264:
|
||||
snprintf(video + strlen(video), sizeof(video) - strlen(video),
|
||||
"%sH.264 (PID:%d", strlen(video) > 0 ? ", " : "",
|
||||
st->st_pid);
|
||||
goto video;
|
||||
|
||||
case SCT_MPEG2AUDIO:
|
||||
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
|
||||
"%sMPEG-2 (PID:%d", strlen(audio) > 0 ? ", " : "",
|
||||
st->st_pid);
|
||||
audio:
|
||||
if(st->st_lang[0]) {
|
||||
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
|
||||
", languange: \"%s\")", st->st_lang);
|
||||
} else {
|
||||
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
|
||||
")");
|
||||
}
|
||||
break;
|
||||
|
||||
case SCT_AC3:
|
||||
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
|
||||
"%sAC3 (PID:%d", strlen(audio) > 0 ? ", " : "",
|
||||
st->st_pid);
|
||||
goto audio;
|
||||
|
||||
case SCT_AAC:
|
||||
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
|
||||
"%sAAC (PID:%d", strlen(audio) > 0 ? ", " : "",
|
||||
st->st_pid);
|
||||
goto audio;
|
||||
|
||||
case SCT_CA:
|
||||
snprintf(scrambling + strlen(scrambling),
|
||||
sizeof(scrambling) - strlen(scrambling),
|
||||
"%s%s", strlen(scrambling) > 0 ? ", " : "",
|
||||
psi_caid2name(st->st_caid));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
htsmsg_add_str(r, "video", video);
|
||||
htsmsg_add_str(r, "audio", audio);
|
||||
htsmsg_add_str(r, "scrambling", scrambling[0] ? scrambling : "none");
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
extjs_channel(http_connection_t *hc, const char *remain, void *opaque)
|
||||
{
|
||||
htsbuf_queue_t *hq = &hc->hc_reply;
|
||||
const char *s = http_arg_get(&hc->hc_req_args, "chid");
|
||||
const char *op = http_arg_get(&hc->hc_req_args, "op");
|
||||
channel_t *ch;
|
||||
channel_t *ch2;
|
||||
th_transport_t *t;
|
||||
int reloadchlist = 0;
|
||||
htsmsg_t *out, *array, *r;
|
||||
channel_tag_mapping_t *ctm;
|
||||
char buf[200];
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
if(http_access_verify(hc, ACCESS_ADMIN)) {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
return HTTP_STATUS_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
ch = s ? channel_find_by_identifier(atoi(s)) : NULL;
|
||||
if(ch == NULL) {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
if(!strcmp(op, "load")) {
|
||||
r = htsmsg_create_map();
|
||||
htsmsg_add_u32(r, "id", ch->ch_id);
|
||||
htsmsg_add_str(r, "name", ch->ch_name);
|
||||
htsmsg_add_str(r, "comdetect", "tt192");
|
||||
|
||||
if(ch->ch_xc != NULL)
|
||||
htsmsg_add_str(r, "xmltvchannel", ch->ch_xc->xc_displayname);
|
||||
|
||||
buf[0] = 0;
|
||||
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
|
||||
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
|
||||
"%s%d", strlen(buf) == 0 ? "" : ",",
|
||||
ctm->ctm_tag->ct_identifier);
|
||||
}
|
||||
htsmsg_add_str(r, "tags", buf);
|
||||
|
||||
out = json_single_record(r, "channels");
|
||||
|
||||
} else if(!strcmp(op, "gettransports")) {
|
||||
out = htsmsg_create_map();
|
||||
array = htsmsg_create_list();
|
||||
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link)
|
||||
htsmsg_add_msg(array, NULL, build_transport_msg(t));
|
||||
|
||||
htsmsg_add_msg(out, "entries", array);
|
||||
|
||||
} else if(!strcmp(op, "delete")) {
|
||||
|
||||
channel_delete(ch);
|
||||
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_u32(out, "reloadchlist", 1);
|
||||
htsmsg_add_u32(out, "success", 1);
|
||||
|
||||
} else if(!strcmp(op, "mergefrom")) {
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "srcch")) == NULL)
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
|
||||
ch2 = channel_find_by_identifier(atoi(s));
|
||||
if(ch2 == NULL || ch2 == ch)
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
|
||||
channel_merge(ch, ch2); /* ch2 goes away here */
|
||||
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_u32(out, "reloadchlist", 1);
|
||||
htsmsg_add_u32(out, "success", 1);
|
||||
|
||||
|
||||
} else if(!strcmp(op, "save")) {
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "tags")) != NULL)
|
||||
channel_set_tags_from_list(ch, s);
|
||||
|
||||
s = http_arg_get(&hc->hc_req_args, "xmltvchannel");
|
||||
channel_set_xmltv_source(ch, s?xmltv_channel_find_by_displayname(s):NULL);
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL &&
|
||||
strcmp(s, ch->ch_name)) {
|
||||
|
||||
if(channel_rename(ch, s)) {
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_u32(out, "success", 0);
|
||||
htsmsg_add_str(out, "errormsg", "Channel name already exist");
|
||||
goto response;
|
||||
} else {
|
||||
reloadchlist = 1;
|
||||
}
|
||||
}
|
||||
|
||||
out = htsmsg_create_map();
|
||||
htsmsg_add_u32(out, "reloadchlist", 1);
|
||||
htsmsg_add_u32(out, "success", 1);
|
||||
|
||||
} else {
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
return HTTP_STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
response:
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
htsmsg_json_serialize(out, hq, 0);
|
||||
htsmsg_destroy(out);
|
||||
|
||||
http_output_content(hc, "text/x-json; charset=UTF-8");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -1528,8 +1379,7 @@ extjs_start(void)
|
|||
http_path_add("/extjs.html", NULL, extjs_root, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/dvbnetworks", NULL, extjs_dvbnetworks, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/channels", NULL, extjs_channels, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/xmltv", NULL, extjs_xmltv, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/channeltags", NULL, extjs_channeltags, ACCESS_WEB_INTERFACE);
|
||||
http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE);
|
||||
|
|
|
@ -8,7 +8,9 @@ tvheadend.channelTags = new Ext.data.JsonStore({
|
|||
fields: ['identifier', 'name'],
|
||||
id: 'identifier',
|
||||
url:'channeltags',
|
||||
baseParams: {op: 'listTags'}
|
||||
baseParams: {
|
||||
op: 'listTags'
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.comet.on('channeltags', function(m) {
|
||||
|
@ -23,9 +25,12 @@ tvheadend.comet.on('channeltags', function(m) {
|
|||
tvheadend.channels = new Ext.data.JsonStore({
|
||||
autoLoad: true,
|
||||
root:'entries',
|
||||
fields: ['name', 'chid'],
|
||||
fields: ['name', 'chid', 'xmltvsrc', 'tags'],
|
||||
id: 'chid',
|
||||
url: "chlist"
|
||||
url: "channels",
|
||||
baseParams: {
|
||||
op: 'list'
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.comet.on('channels', function(m) {
|
||||
|
@ -34,343 +39,198 @@ tvheadend.comet.on('channels', function(m) {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Channel details
|
||||
*/
|
||||
tvheadend.channeldetails = function(chid, chname) {
|
||||
var fm = Ext.form;
|
||||
var xg = Ext.grid;
|
||||
|
||||
var expander = new xg.RowExpander({
|
||||
tpl : new Ext.Template(
|
||||
'<div><b width=100px>Video:</b>{video}</div>',
|
||||
'<div><b>Audio:</b>{audio}</div>',
|
||||
'<div><b>Subtitling:</b>{subtitles}</div>',
|
||||
'<div><b>Scrambling:</b>{scrambling}</div>'
|
||||
)
|
||||
});
|
||||
|
||||
var enabledColumn = new Ext.grid.CheckColumn({
|
||||
header: "Enabled",
|
||||
dataIndex: 'enabled',
|
||||
width: 60
|
||||
});
|
||||
|
||||
var cm = new Ext.grid.ColumnModel([expander,
|
||||
enabledColumn,
|
||||
{
|
||||
width: 125,
|
||||
id:'name',
|
||||
header: "Original name",
|
||||
dataIndex: 'name'
|
||||
},{
|
||||
width: 125,
|
||||
id:'status',
|
||||
header: "Last status",
|
||||
dataIndex: 'status'
|
||||
},{
|
||||
width: 125,
|
||||
id:'provider',
|
||||
header: "Provider",
|
||||
dataIndex: 'provider'
|
||||
},{
|
||||
width: 125,
|
||||
id:'network',
|
||||
header: "Network",
|
||||
dataIndex: 'network'
|
||||
},{
|
||||
width: 250,
|
||||
id:'source',
|
||||
header: "Source",
|
||||
dataIndex: 'source'
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
var transportRecord = Ext.data.Record.create([
|
||||
{name: 'enabled'},
|
||||
{name: 'status'},
|
||||
{name: 'name'},
|
||||
{name: 'provider'},
|
||||
{name: 'network'},
|
||||
{name: 'source'},
|
||||
{name: 'video'},
|
||||
{name: 'audio'},
|
||||
{name: 'scrambling'},
|
||||
{name: 'subtitles'}
|
||||
]);
|
||||
|
||||
var transportsstore =
|
||||
new Ext.data.JsonStore({root: 'entries',
|
||||
fields: transportRecord,
|
||||
url: "channel",
|
||||
autoLoad: true,
|
||||
id: 'id',
|
||||
storeid: 'id',
|
||||
baseParams: {chid: chid, op: "gettransports"}
|
||||
});
|
||||
|
||||
|
||||
var transportsgrid = new Ext.grid.EditorGridPanel({
|
||||
title:'Transports',
|
||||
anchor: '100% 50%',
|
||||
stripeRows:true,
|
||||
plugins:[enabledColumn, expander],
|
||||
store: transportsstore,
|
||||
clicksToEdit: 2,
|
||||
viewConfig: {forceFit:true},
|
||||
cm: cm,
|
||||
selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
|
||||
});
|
||||
|
||||
|
||||
var confreader = new Ext.data.JsonReader({
|
||||
root: 'channels'
|
||||
}, ['name','xmltvchannel','tags']);
|
||||
|
||||
|
||||
var xmltvChannels = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
fields: [{name: 'xcTitle'}, {name: 'xcIcon'}],
|
||||
url:'xmltv',
|
||||
baseParams: {op: 'listChannels'}
|
||||
});
|
||||
|
||||
|
||||
var confpanel = new Ext.FormPanel({
|
||||
border:false,
|
||||
disabled:true,
|
||||
bodyStyle:'padding:15px',
|
||||
anchor: '100% 50%',
|
||||
labelAlign: 'right',
|
||||
labelWidth: 150,
|
||||
waitMsgTarget: true,
|
||||
reader: confreader,
|
||||
|
||||
items: [{
|
||||
layout:'column',
|
||||
border:false,
|
||||
items:[{
|
||||
border:false,
|
||||
columnWidth:.5,
|
||||
layout: 'form',
|
||||
defaultType: 'textfield',
|
||||
items: [
|
||||
{
|
||||
fieldLabel: 'Channel name',
|
||||
name: 'name'
|
||||
},new Ext.form.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
fieldLabel: 'XML-TV Source',
|
||||
name: 'xmltvchannel',
|
||||
width: 200,
|
||||
displayField:'xcTitle',
|
||||
valueField:'xcTitle',
|
||||
store: xmltvChannels,
|
||||
forceSelection: true,
|
||||
mode: 'remote',
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'None'
|
||||
})
|
||||
]
|
||||
},{
|
||||
border:false,
|
||||
columnWidth:.5,
|
||||
layout: 'form',
|
||||
items: [{
|
||||
fieldLabel: 'Tags',
|
||||
xtype:"multiselect",
|
||||
name:"tags",
|
||||
valueField:"identifier",
|
||||
displayField:"name",
|
||||
width:200,
|
||||
height:200,
|
||||
store:tvheadend.channelTags
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
confpanel.getForm().load({url:'channel',
|
||||
params:{'chid': chid, 'op':'load'},
|
||||
success:function(form, action) {
|
||||
confpanel.enable();
|
||||
}});
|
||||
|
||||
|
||||
function saveChanges() {
|
||||
confpanel.getForm().submit({url:'channel',
|
||||
params:{'chid': chid, 'op':'save'},
|
||||
waitMsg:'Saving Data...',
|
||||
failure: function(form, action) {
|
||||
Ext.Msg.alert('Save failed', action.result.errormsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteChannel() {
|
||||
Ext.MessageBox.confirm('Message',
|
||||
'Do you really want to delete "' + chname + '"',
|
||||
function(button) {
|
||||
if(button == 'no')
|
||||
return;
|
||||
Ext.Ajax.request({url: 'channel',
|
||||
params:{'chid': chid, 'op':'delete'},
|
||||
success: function() {
|
||||
panel.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var panel = new Ext.Panel({
|
||||
title: chname,
|
||||
border:false,
|
||||
tbar: [{
|
||||
tooltip: 'Delete channel "' + chname + '". All mapped transports will be unmapped',
|
||||
iconCls:'remove',
|
||||
text: "Delete channel",
|
||||
handler: deleteChannel
|
||||
}, '-', {
|
||||
tooltip: 'Save changes made to channel configuration below and the mapped transports',
|
||||
iconCls:'save',
|
||||
text: "Save configuration",
|
||||
handler: saveChanges
|
||||
}, '->', {
|
||||
text: 'Help',
|
||||
handler: function() {
|
||||
new tvheadend.help('Channel configuration',
|
||||
'config_channels.html');
|
||||
}
|
||||
}],
|
||||
defaults: {
|
||||
border:false
|
||||
},
|
||||
layout:'anchor',
|
||||
items: [confpanel,transportsgrid]
|
||||
});
|
||||
|
||||
|
||||
panel.on('afterlayout', function(parent, n) {
|
||||
var DropTargetEl = parent.body.dom;
|
||||
|
||||
var DropTarget = new Ext.dd.DropTarget(DropTargetEl, {
|
||||
ddGroup : 'chconfddgroup',
|
||||
notifyEnter : function(ddSource, e, data) {
|
||||
|
||||
//Add some flare to invite drop.
|
||||
parent.body.stopFx();
|
||||
parent.body.highlight();
|
||||
},
|
||||
notifyDrop : function(ddSource, e, data){
|
||||
|
||||
// Reference the record (single selection) for readability
|
||||
var selectedRecord = ddSource.dragData.selections[0];
|
||||
|
||||
Ext.MessageBox.confirm('Merge channels',
|
||||
'Copy transport configuration from "' + selectedRecord.data.name +
|
||||
'" to "' + chname + '". This will also remove the channel "' +
|
||||
selectedRecord.data.name + '"',
|
||||
function(button) {
|
||||
if(button == 'no')
|
||||
return;
|
||||
Ext.Ajax.request({url: 'channel',
|
||||
params:{chid: chid,
|
||||
op:'mergefrom',
|
||||
srcch: selectedRecord.data.chid},
|
||||
success: function() {
|
||||
transportsstore.reload();
|
||||
}});
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tvheadend.chconf = function() {
|
||||
var chlist = new Ext.grid.GridPanel({
|
||||
viewConfig: {forceFit:true},
|
||||
ddGroup: 'chconfddgroup',
|
||||
enableDragDrop: true,
|
||||
stripeRows:true,
|
||||
region:'west',
|
||||
width: 300,
|
||||
columns: [{id:'name',
|
||||
header: "Channel name",
|
||||
width: 260,
|
||||
dataIndex: 'name'}
|
||||
],
|
||||
selModel: new Ext.grid.RowSelectionModel({singleSelect:true}),
|
||||
store: tvheadend.channels
|
||||
});
|
||||
|
||||
var details = new Ext.Panel({
|
||||
region:'center', layout:'fit',
|
||||
items:[{border: false}]
|
||||
tvheadend.chconf = function()
|
||||
{
|
||||
var xmltvChannels = new Ext.data.JsonStore({
|
||||
root:'entries',
|
||||
fields: ['xcTitle','xcIcon'],
|
||||
url:'xmltv',
|
||||
baseParams: {
|
||||
op: 'listChannels'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var panel = new Ext.Panel({
|
||||
border: false,
|
||||
title:'Channels',
|
||||
layout:'border',
|
||||
items: [chlist, details]
|
||||
});
|
||||
var fm = Ext.form;
|
||||
|
||||
var cm = new Ext.grid.ColumnModel([
|
||||
{
|
||||
header: "Name",
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
editor: new fm.TextField({
|
||||
allowBlank: false
|
||||
})
|
||||
},
|
||||
{
|
||||
header: "XMLTV source",
|
||||
dataIndex: 'xmltvsrc',
|
||||
width: 150,
|
||||
editor: new fm.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
store: xmltvChannels,
|
||||
allowBlank: true,
|
||||
typeAhead: true,
|
||||
minChars: 2,
|
||||
lazyRender: true,
|
||||
triggerAction: 'all',
|
||||
mode: 'remote',
|
||||
displayField:'xcTitle',
|
||||
valueField:'xcTitle'
|
||||
})
|
||||
},
|
||||
{
|
||||
header: "Tags",
|
||||
dataIndex: 'tags',
|
||||
width: 300,
|
||||
renderer: function(value, metadata, record, row, col, store) {
|
||||
if (typeof value === 'undefined' || value.length < 1) {
|
||||
return '<span class="tvh-grid-unset">No tags</span>';
|
||||
}
|
||||
|
||||
chlist.on('rowclick', function(grid, n) {
|
||||
var rec = tvheadend.channels.getAt(n);
|
||||
|
||||
details.remove(details.getComponent(0));
|
||||
details.doLayout();
|
||||
|
||||
var newpanel = new tvheadend.channeldetails(rec.data.chid,
|
||||
rec.data.name);
|
||||
|
||||
details.add(newpanel);
|
||||
details.doLayout();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Setup Drop Targets
|
||||
*/
|
||||
|
||||
// This will make sure we only drop to the view container
|
||||
|
||||
/*
|
||||
var DropTargetEl = details.getView();
|
||||
|
||||
var DropTarget = new Ext.dd.DropTarget(DropTargetEl, {
|
||||
ddGroup : 'chconfddgroup',
|
||||
notifyEnter : function(ddSource, e, data) {
|
||||
|
||||
//Add some flare to invite drop.
|
||||
panel.body.stopFx();
|
||||
panel.body.highlight();
|
||||
ret = [];
|
||||
tags = value.split(',');
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
var tag = tvheadend.channelTags.getById(tags[i]);
|
||||
if (typeof tag !== 'undefined') {
|
||||
ret.push(tag.data.name);
|
||||
}
|
||||
}
|
||||
return ret.join(', ');
|
||||
},
|
||||
notifyDrop : function(ddSource, e, data){
|
||||
|
||||
// Reference the record (single selection) for readability
|
||||
var selectedRecord = ddSource.dragData.selections[0];
|
||||
|
||||
console.log(selectedRecord);
|
||||
}
|
||||
});
|
||||
editor: new Ext.ux.form.LovCombo({
|
||||
store: tvheadend.channelTags,
|
||||
mode:'local',
|
||||
valueField: 'identifier',
|
||||
displayField: 'name'
|
||||
})
|
||||
}
|
||||
]);
|
||||
|
||||
*/
|
||||
/*
|
||||
details.on('afterlayout', function(parent, n) {
|
||||
console.log(parent);
|
||||
|
||||
function delSelected() {
|
||||
var selectedKeys = grid.selModel.selections.keys;
|
||||
if(selectedKeys.length > 0) {
|
||||
Ext.MessageBox.confirm('Message',
|
||||
'Do you really want to delete selection?',
|
||||
deleteRecord);
|
||||
} else {
|
||||
Ext.MessageBox.alert('Message',
|
||||
'Please select at least one item to delete');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function deleteRecord(btn) {
|
||||
if(btn=='yes') {
|
||||
var selectedKeys = grid.selModel.selections.keys;
|
||||
|
||||
Ext.Ajax.request({
|
||||
url: "channels",
|
||||
params: {
|
||||
op:"delete",
|
||||
entries:Ext.encode(selectedKeys)
|
||||
},
|
||||
failure:function(response,options) {
|
||||
Ext.MessageBox.alert('Server Error','Unable to delete');
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function saveChanges() {
|
||||
var mr = tvheadend.channels.getModifiedRecords();
|
||||
var out = new Array();
|
||||
for (var x = 0; x < mr.length; x++) {
|
||||
v = mr[x].getChanges();
|
||||
out[x] = v;
|
||||
out[x].id = mr[x].id;
|
||||
}
|
||||
|
||||
Ext.Ajax.request({
|
||||
url: "channels",
|
||||
params: {
|
||||
op:"update",
|
||||
entries:Ext.encode(out)
|
||||
},
|
||||
success:function(response,options) {
|
||||
tvheadend.channels.commitChanges();
|
||||
},
|
||||
failure:function(response,options) {
|
||||
Ext.MessageBox.alert('Message', response.statusText);
|
||||
}
|
||||
});
|
||||
*/
|
||||
return panel;
|
||||
}
|
||||
|
||||
var selModel = new Ext.grid.RowSelectionModel({
|
||||
singleSelect:false
|
||||
});
|
||||
|
||||
var delBtn = new Ext.Toolbar.Button({
|
||||
tooltip: 'Delete one or more selected channels',
|
||||
iconCls:'remove',
|
||||
text: 'Delete selected',
|
||||
handler: delSelected,
|
||||
disabled: true
|
||||
});
|
||||
|
||||
selModel.on('selectionchange', function(s) {
|
||||
delBtn.setDisabled(s.getCount() == 0);
|
||||
});
|
||||
|
||||
var saveBtn = new Ext.Toolbar.Button({
|
||||
tooltip: 'Save any changes made (Changed cells have red borders).',
|
||||
iconCls:'save',
|
||||
text: "Save changes",
|
||||
handler: saveChanges,
|
||||
disabled: true
|
||||
});
|
||||
|
||||
var rejectBtn = new Ext.Toolbar.Button({
|
||||
tooltip: 'Revert any changes made (Changed cells have red borders).',
|
||||
iconCls:'undo',
|
||||
text: "Revert changes",
|
||||
handler: function() {
|
||||
tvheadend.channels.rejectChanges();
|
||||
},
|
||||
disabled: true
|
||||
});
|
||||
|
||||
|
||||
var grid = new Ext.grid.EditorGridPanel({
|
||||
stripeRows: true,
|
||||
title: 'Channels',
|
||||
store: tvheadend.channels,
|
||||
clicksToEdit: 2,
|
||||
cm: cm,
|
||||
viewConfig: {
|
||||
forceFit:true
|
||||
},
|
||||
selModel: selModel,
|
||||
tbar: [
|
||||
delBtn, '-', saveBtn, rejectBtn, '->', {
|
||||
text: 'Help',
|
||||
handler: function() {
|
||||
new tvheadend.help(title, helpContent);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
tvheadend.channels.on('update', function(s, r, o) {
|
||||
d = s.getModifiedRecords().length == 0
|
||||
saveBtn.setDisabled(d);
|
||||
rejectBtn.setDisabled(d);
|
||||
});
|
||||
|
||||
tvheadend.channelTags.on('load', function(s, r, o) {
|
||||
if(grid.rendered)
|
||||
grid.getView().refresh();
|
||||
});
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
|
|
@ -273,4 +273,35 @@
|
|||
float:left;
|
||||
}
|
||||
|
||||
/* eof */
|
||||
/** vim: ts=4:sw=4:nu:fdc=4:nospell
|
||||
*
|
||||
* Ext.ux.form.LovCombo CSS File
|
||||
*
|
||||
* @author Ing.Jozef Sakáloš
|
||||
* @copyright (c) 2008, by Ing. Jozef Sakáloš
|
||||
* @date 5. April 2008
|
||||
* @version $Id: Ext.ux.form.LovCombo.css 189 2008-04-16 21:01:06Z jozo $
|
||||
*
|
||||
* @license Ext.ux.form.LovCombo.css is licensed under the terms of the Open Source
|
||||
* LGPL 3.0 license. Commercial use is permitted to the extent that the
|
||||
* code/component(s) do NOT become part of another Open Source or Commercially
|
||||
* licensed development library or toolkit without explicit permission.
|
||||
*
|
||||
* License details: http://www.gnu.org/licenses/lgpl.html
|
||||
*/
|
||||
|
||||
.ux-lovcombo-icon {
|
||||
width:16px;
|
||||
height:16px;
|
||||
float:left;
|
||||
background-position: -1px -1px ! important;
|
||||
background-repeat:no-repeat ! important;
|
||||
}
|
||||
.ux-lovcombo-icon-checked {
|
||||
background: transparent url(../extjs/resources/images/default/menu/checked.gif);
|
||||
}
|
||||
.ux-lovcombo-icon-unchecked {
|
||||
background: transparent url(../extjs/resources/images/default/menu/unchecked.gif);
|
||||
}
|
||||
|
||||
/* eof */
|
||||
|
|
|
@ -179,610 +179,6 @@ Ext.extend(Ext.grid.RowExpander, Ext.util.Observable, {
|
|||
|
||||
|
||||
|
||||
/*
|
||||
* Software License Agreement (BSD License)
|
||||
* Copyright (c) 2008, Nige "Animal" White
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of the original author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/**
|
||||
* @class Ext.ux.DDView
|
||||
* <p>A DnD-enabled version of {@link Ext.DataView}. Drag/drop is implemented by adding
|
||||
* {@link Ext.data.Record}s to the target DDView. If copying is not being performed,
|
||||
* the original {@link Ext.data.Record} is removed from the source DDView.</p>
|
||||
* @constructor
|
||||
* Create a new DDView
|
||||
* @param {Object} config The configuration properties.
|
||||
*/
|
||||
Ext.ux.DDView = function(config) {
|
||||
if (!config.itemSelector) {
|
||||
var tpl = config.tpl;
|
||||
if (this.classRe.test(tpl)) {
|
||||
config.tpl = tpl.replace(this.classRe, 'class=$1x-combo-list-item $2$1');
|
||||
}
|
||||
else {
|
||||
config.tpl = tpl.replace(this.tagRe, '$1 class="x-combo-list-item" $2');
|
||||
}
|
||||
config.itemSelector = ".x-combo-list-item";
|
||||
}
|
||||
Ext.ux.DDView.superclass.constructor.call(this, Ext.apply(config, {
|
||||
border: false
|
||||
}));
|
||||
};
|
||||
|
||||
Ext.extend(Ext.ux.DDView, Ext.DataView, {
|
||||
/**
|
||||
* @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone (defaults to undefined).
|
||||
*/
|
||||
/**
|
||||
* @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone (defaults to undefined).
|
||||
*/
|
||||
/**
|
||||
* @cfg {Boolean} copy Causes drag operations to copy nodes rather than move (defaults to false).
|
||||
*/
|
||||
/**
|
||||
* @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move (defaults to false).
|
||||
*/
|
||||
/**
|
||||
* @cfg {String} sortDir Sort direction for the view, 'ASC' or 'DESC' (defaults to 'ASC').
|
||||
*/
|
||||
sortDir: 'ASC',
|
||||
|
||||
// private
|
||||
isFormField: true,
|
||||
classRe: /class=(['"])(.*)\1/,
|
||||
tagRe: /(<\w*)(.*?>)/,
|
||||
reset: Ext.emptyFn,
|
||||
clearInvalid: Ext.form.Field.prototype.clearInvalid,
|
||||
|
||||
// private
|
||||
afterRender: function() {
|
||||
Ext.ux.DDView.superclass.afterRender.call(this);
|
||||
if (this.dragGroup) {
|
||||
this.setDraggable(this.dragGroup.split(","));
|
||||
}
|
||||
if (this.dropGroup) {
|
||||
this.setDroppable(this.dropGroup.split(","));
|
||||
}
|
||||
if (this.deletable) {
|
||||
this.setDeletable();
|
||||
}
|
||||
this.isDirtyFlag = false;
|
||||
this.addEvents(
|
||||
"drop"
|
||||
);
|
||||
},
|
||||
|
||||
// private
|
||||
validate: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
// private
|
||||
destroy: function() {
|
||||
this.purgeListeners();
|
||||
this.getEl().removeAllListeners();
|
||||
this.getEl().remove();
|
||||
if (this.dragZone) {
|
||||
if (this.dragZone.destroy) {
|
||||
this.dragZone.destroy();
|
||||
}
|
||||
}
|
||||
if (this.dropZone) {
|
||||
if (this.dropZone.destroy) {
|
||||
this.dropZone.destroy();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows this class to be an Ext.form.Field so it can be found using {@link Ext.form.BasicForm#findField}.
|
||||
*/
|
||||
getName: function() {
|
||||
return this.name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the View from a JSON string representing the Records to put into the Store.
|
||||
* @param {String} value The JSON string
|
||||
*/
|
||||
setValue: function(v) {
|
||||
if (!this.store) {
|
||||
throw "DDView.setValue(). DDView must be constructed with a valid Store";
|
||||
}
|
||||
var data = {};
|
||||
data[this.store.reader.meta.root] = v ? [].concat(v) : [];
|
||||
this.store.proxy = new Ext.data.MemoryProxy(data);
|
||||
this.store.load();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the view's data value as a list of ids.
|
||||
* @return {String} A parenthesised list of the ids of the Records in the View, e.g. (1,3,8).
|
||||
*/
|
||||
getValue: function() {
|
||||
var result = '(';
|
||||
this.store.each(function(rec) {
|
||||
result += rec.id + ',';
|
||||
});
|
||||
return result.substr(0, result.length - 1) + ')';
|
||||
},
|
||||
|
||||
getIds: function() {
|
||||
var i = 0, result = new Array(this.store.getCount());
|
||||
this.store.each(function(rec) {
|
||||
result[i++] = rec.id;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the view's data has changed, else false.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isDirty: function() {
|
||||
return this.isDirtyFlag;
|
||||
},
|
||||
|
||||
/**
|
||||
* Part of the Ext.dd.DropZone interface. If no target node is found, the
|
||||
* whole Element becomes the target, and this causes the drop gesture to append.
|
||||
*/
|
||||
getTargetFromEvent : function(e) {
|
||||
var target = e.getTarget();
|
||||
while ((target !== null) && (target.parentNode != this.el.dom)) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (!target) {
|
||||
target = this.el.dom.lastChild || this.el.dom;
|
||||
}
|
||||
return target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the drag data which consists of an object which has the property "ddel" as
|
||||
* the drag proxy element.
|
||||
*/
|
||||
getDragData : function(e) {
|
||||
var target = this.findItemFromChild(e.getTarget());
|
||||
if(target) {
|
||||
if (!this.isSelected(target)) {
|
||||
delete this.ignoreNextClick;
|
||||
this.onItemClick(target, this.indexOf(target), e);
|
||||
this.ignoreNextClick = true;
|
||||
}
|
||||
var dragData = {
|
||||
sourceView: this,
|
||||
viewNodes: [],
|
||||
records: [],
|
||||
copy: this.copy || (this.allowCopy && e.ctrlKey)
|
||||
};
|
||||
if (this.getSelectionCount() == 1) {
|
||||
var i = this.getSelectedIndexes()[0];
|
||||
var n = this.getNode(i);
|
||||
dragData.viewNodes.push(dragData.ddel = n);
|
||||
dragData.records.push(this.store.getAt(i));
|
||||
dragData.repairXY = Ext.fly(n).getXY();
|
||||
} else {
|
||||
dragData.ddel = document.createElement('div');
|
||||
dragData.ddel.className = 'multi-proxy';
|
||||
this.collectSelection(dragData);
|
||||
}
|
||||
return dragData;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// override the default repairXY.
|
||||
getRepairXY : function(e){
|
||||
return this.dragData.repairXY;
|
||||
},
|
||||
|
||||
// private
|
||||
collectSelection: function(data) {
|
||||
data.repairXY = Ext.fly(this.getSelectedNodes()[0]).getXY();
|
||||
if (this.preserveSelectionOrder === true) {
|
||||
Ext.each(this.getSelectedIndexes(), function(i) {
|
||||
var n = this.getNode(i);
|
||||
var dragNode = n.cloneNode(true);
|
||||
dragNode.id = Ext.id();
|
||||
data.ddel.appendChild(dragNode);
|
||||
data.records.push(this.store.getAt(i));
|
||||
data.viewNodes.push(n);
|
||||
}, this);
|
||||
} else {
|
||||
var i = 0;
|
||||
this.store.each(function(rec){
|
||||
if (this.isSelected(i)) {
|
||||
var n = this.getNode(i);
|
||||
var dragNode = n.cloneNode(true);
|
||||
dragNode.id = Ext.id();
|
||||
data.ddel.appendChild(dragNode);
|
||||
data.records.push(this.store.getAt(i));
|
||||
data.viewNodes.push(n);
|
||||
}
|
||||
i++;
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Specify to which ddGroup items in this DDView may be dragged.
|
||||
* @param {String} ddGroup The DD group name to assign this view to.
|
||||
*/
|
||||
setDraggable: function(ddGroup) {
|
||||
if (ddGroup instanceof Array) {
|
||||
Ext.each(ddGroup, this.setDraggable, this);
|
||||
return;
|
||||
}
|
||||
if (this.dragZone) {
|
||||
this.dragZone.addToGroup(ddGroup);
|
||||
} else {
|
||||
this.dragZone = new Ext.dd.DragZone(this.getEl(), {
|
||||
containerScroll: true,
|
||||
ddGroup: ddGroup
|
||||
});
|
||||
// Draggability implies selection. DragZone's mousedown selects the element.
|
||||
if (!this.multiSelect) { this.singleSelect = true; }
|
||||
|
||||
// Wire the DragZone's handlers up to methods in *this*
|
||||
this.dragZone.getDragData = this.getDragData.createDelegate(this);
|
||||
this.dragZone.getRepairXY = this.getRepairXY;
|
||||
this.dragZone.onEndDrag = this.onEndDrag;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Specify from which ddGroup this DDView accepts drops.
|
||||
* @param {String} ddGroup The DD group name from which to accept drops.
|
||||
*/
|
||||
setDroppable: function(ddGroup) {
|
||||
if (ddGroup instanceof Array) {
|
||||
Ext.each(ddGroup, this.setDroppable, this);
|
||||
return;
|
||||
}
|
||||
if (this.dropZone) {
|
||||
this.dropZone.addToGroup(ddGroup);
|
||||
} else {
|
||||
this.dropZone = new Ext.dd.DropZone(this.getEl(), {
|
||||
owningView: this,
|
||||
containerScroll: true,
|
||||
ddGroup: ddGroup
|
||||
});
|
||||
|
||||
// Wire the DropZone's handlers up to methods in *this*
|
||||
this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
|
||||
this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
|
||||
this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
|
||||
this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
|
||||
this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
getDropPoint : function(e, n, dd){
|
||||
if (n == this.el.dom) { return "above"; }
|
||||
var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;
|
||||
var c = t + (b - t) / 2;
|
||||
var y = Ext.lib.Event.getPageY(e);
|
||||
if(y <= c) {
|
||||
return "above";
|
||||
}else{
|
||||
return "below";
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
isValidDropPoint: function(pt, n, data) {
|
||||
if (!data.viewNodes || (data.viewNodes.length != 1)) {
|
||||
return true;
|
||||
}
|
||||
var d = data.viewNodes[0];
|
||||
if (d == n) {
|
||||
return false;
|
||||
}
|
||||
if ((pt == "below") && (n.nextSibling == d)) {
|
||||
return false;
|
||||
}
|
||||
if ((pt == "above") && (n.previousSibling == d)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// private
|
||||
onNodeEnter : function(n, dd, e, data){
|
||||
if (this.highlightColor && (data.sourceView != this)) {
|
||||
this.el.highlight(this.highlightColor);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// private
|
||||
onNodeOver : function(n, dd, e, data){
|
||||
var dragElClass = this.dropNotAllowed;
|
||||
var pt = this.getDropPoint(e, n, dd);
|
||||
if (this.isValidDropPoint(pt, n, data)) {
|
||||
if (this.appendOnly || this.sortField) {
|
||||
return "x-tree-drop-ok-below";
|
||||
}
|
||||
|
||||
// set the insert point style on the target node
|
||||
if (pt) {
|
||||
var targetElClass;
|
||||
if (pt == "above"){
|
||||
dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
|
||||
targetElClass = "x-view-drag-insert-above";
|
||||
} else {
|
||||
dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
|
||||
targetElClass = "x-view-drag-insert-below";
|
||||
}
|
||||
if (this.lastInsertClass != targetElClass){
|
||||
Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);
|
||||
this.lastInsertClass = targetElClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dragElClass;
|
||||
},
|
||||
|
||||
// private
|
||||
onNodeOut : function(n, dd, e, data){
|
||||
this.removeDropIndicators(n);
|
||||
},
|
||||
|
||||
// private
|
||||
onNodeDrop : function(n, dd, e, data){
|
||||
if (this.fireEvent("drop", this, n, dd, e, data) === false) {
|
||||
return false;
|
||||
}
|
||||
var pt = this.getDropPoint(e, n, dd);
|
||||
var insertAt = (this.appendOnly || (n == this.el.dom)) ? this.store.getCount() : n.viewIndex;
|
||||
if (pt == "below") {
|
||||
insertAt++;
|
||||
}
|
||||
|
||||
// Validate if dragging within a DDView
|
||||
if (data.sourceView == this) {
|
||||
// If the first element to be inserted below is the target node, remove it
|
||||
if (pt == "below") {
|
||||
if (data.viewNodes[0] == n) {
|
||||
data.viewNodes.shift();
|
||||
}
|
||||
} else { // If the last element to be inserted above is the target node, remove it
|
||||
if (data.viewNodes[data.viewNodes.length - 1] == n) {
|
||||
data.viewNodes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to drop...
|
||||
if (!data.viewNodes.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we are moving DOWN, then because a store.remove() takes place first,
|
||||
// the insertAt must be decremented.
|
||||
if (insertAt > this.store.indexOf(data.records[0])) {
|
||||
insertAt--;
|
||||
}
|
||||
}
|
||||
|
||||
// Dragging from a Tree. Use the Tree's recordFromNode function.
|
||||
if (data.node instanceof Ext.tree.TreeNode) {
|
||||
var r = data.node.getOwnerTree().recordFromNode(data.node);
|
||||
if (r) {
|
||||
data.records = [ r ];
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.records) {
|
||||
alert("Programming problem. Drag data contained no Records");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.records.length; i++) {
|
||||
var r = data.records[i];
|
||||
var dup = this.store.getById(r.id);
|
||||
if (dup && (dd != this.dragZone)) {
|
||||
if(!this.allowDup && !this.allowTrash){
|
||||
Ext.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
|
||||
return true
|
||||
}
|
||||
var x=new Ext.data.Record();
|
||||
r.id=x.id;
|
||||
delete x;
|
||||
}
|
||||
if (data.copy) {
|
||||
this.store.insert(insertAt++, r.copy());
|
||||
} else {
|
||||
if (data.sourceView) {
|
||||
data.sourceView.isDirtyFlag = true;
|
||||
data.sourceView.store.remove(r);
|
||||
}
|
||||
if(!this.allowTrash)this.store.insert(insertAt++, r);
|
||||
}
|
||||
if(this.sortField){
|
||||
this.store.sort(this.sortField, this.sortDir);
|
||||
}
|
||||
this.isDirtyFlag = true;
|
||||
}
|
||||
this.dragZone.cachedTarget = null;
|
||||
return true;
|
||||
},
|
||||
|
||||
// private
|
||||
onEndDrag: function(data, e) {
|
||||
var d = Ext.get(this.dragData.ddel);
|
||||
if (d && d.hasClass("multi-proxy")) {
|
||||
d.remove();
|
||||
//delete this.dragData.ddel;
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
removeDropIndicators : function(n){
|
||||
if(n){
|
||||
Ext.fly(n).removeClass([
|
||||
"x-view-drag-insert-above",
|
||||
"x-view-drag-insert-left",
|
||||
"x-view-drag-insert-right",
|
||||
"x-view-drag-insert-below"]);
|
||||
this.lastInsertClass = "_noclass";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a delete option to the DDView's context menu.
|
||||
* @param {String} imageUrl The URL of the "delete" icon image.
|
||||
*/
|
||||
setDeletable: function(imageUrl) {
|
||||
if (!this.singleSelect && !this.multiSelect) {
|
||||
this.singleSelect = true;
|
||||
}
|
||||
var c = this.getContextMenu();
|
||||
this.contextMenu.on("itemclick", function(item) {
|
||||
switch (item.id) {
|
||||
case "delete":
|
||||
this.remove(this.getSelectedIndexes());
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
this.contextMenu.add({
|
||||
icon: imageUrl || AU.resolveUrl("/images/delete.gif"),
|
||||
id: "delete",
|
||||
text: AU.getMessage("deleteItem")
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the context menu for this DDView.
|
||||
* @return {Ext.menu.Menu} The context menu
|
||||
*/
|
||||
getContextMenu: function() {
|
||||
if (!this.contextMenu) {
|
||||
// Create the View's context menu
|
||||
this.contextMenu = new Ext.menu.Menu({
|
||||
id: this.id + "-contextmenu"
|
||||
});
|
||||
this.el.on("contextmenu", this.showContextMenu, this);
|
||||
}
|
||||
return this.contextMenu;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables the view's context menu.
|
||||
*/
|
||||
disableContextMenu: function() {
|
||||
if (this.contextMenu) {
|
||||
this.el.un("contextmenu", this.showContextMenu, this);
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
showContextMenu: function(e, item) {
|
||||
item = this.findItemFromChild(e.getTarget());
|
||||
if (item) {
|
||||
e.stopEvent();
|
||||
this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
|
||||
this.contextMenu.showAt(e.getXY());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove {@link Ext.data.Record}s at the specified indices.
|
||||
* @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
|
||||
*/
|
||||
remove: function(selectedIndices) {
|
||||
selectedIndices = [].concat(selectedIndices);
|
||||
for (var i = 0; i < selectedIndices.length; i++) {
|
||||
var rec = this.store.getAt(selectedIndices[i]);
|
||||
this.store.remove(rec);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Double click fires the {@link #dblclick} event. Additionally, if this DDView is draggable, and there is only one other
|
||||
* related DropZone that is in another DDView, it drops the selected node on that DDView.
|
||||
*/
|
||||
onDblClick : function(e){
|
||||
var item = this.findItemFromChild(e.getTarget());
|
||||
if(item){
|
||||
if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
|
||||
return false;
|
||||
}
|
||||
if (this.dragGroup) {
|
||||
var targets = Ext.dd.DragDropMgr.getRelated(this.dragZone, true);
|
||||
|
||||
// Remove instances of this View's DropZone
|
||||
while (targets.indexOf(this.dropZone) !== -1) {
|
||||
targets.remove(this.dropZone);
|
||||
}
|
||||
|
||||
// If there's only one other DropZone, and it is owned by a DDView, then drop it in
|
||||
if ((targets.length == 1) && (targets[0].owningView)) {
|
||||
this.dragZone.cachedTarget = null;
|
||||
var el = Ext.get(targets[0].getEl());
|
||||
var box = el.getBox(true);
|
||||
targets[0].onNodeDrop(el.dom, {
|
||||
target: el.dom,
|
||||
xy: [box.x, box.y + box.height - 1]
|
||||
}, null, this.getDragData(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
onItemClick : function(item, index, e){
|
||||
// The DragZone's mousedown->getDragData already handled selection
|
||||
if (this.ignoreNextClick) {
|
||||
delete this.ignoreNextClick;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.fireEvent("beforeclick", this, index, item, e) === false){
|
||||
return false;
|
||||
}
|
||||
if(this.multiSelect || this.singleSelect){
|
||||
if(this.multiSelect && e.shiftKey && this.lastSelection){
|
||||
this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
|
||||
} else if (this.isSelected(item) && e.ctrlKey) {
|
||||
this.deselect(item);
|
||||
}else{
|
||||
this.deselect(item);
|
||||
this.select(item, this.multiSelect && e.ctrlKey);
|
||||
this.lastSelection = item;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Ext JS Library 2.2
|
||||
* Copyright(c) 2006-2008, Ext JS, LLC.
|
||||
|
@ -1602,4 +998,286 @@ Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, {
|
|||
// registre xtype
|
||||
Ext.reg('rowactions', Ext.ux.grid.RowActions);
|
||||
|
||||
// eof
|
||||
|
||||
/**
|
||||
* Ext.ux.form.LovCombo, List of Values Combo
|
||||
*
|
||||
* @author Ing. Jozef Sakáloš
|
||||
* @copyright (c) 2008, by Ing. Jozef Sakáloš
|
||||
* @date 16. April 2008
|
||||
* @version $Id: Ext.ux.form.LovCombo.js 285 2008-06-06 09:22:20Z jozo $
|
||||
*
|
||||
* @license Ext.ux.form.LovCombo.js is licensed under the terms of the Open Source
|
||||
* LGPL 3.0 license. Commercial use is permitted to the extent that the
|
||||
* code/component(s) do NOT become part of another Open Source or Commercially
|
||||
* licensed development library or toolkit without explicit permission.
|
||||
*
|
||||
* License details: http://www.gnu.org/licenses/lgpl.html
|
||||
*/
|
||||
|
||||
/*global Ext */
|
||||
|
||||
// add RegExp.escape if it has not been already added
|
||||
if('function' !== typeof RegExp.escape) {
|
||||
RegExp.escape = function(s) {
|
||||
if('string' !== typeof s) {
|
||||
return s;
|
||||
}
|
||||
// Note: if pasting from forum, precede ]/\ with backslash manually
|
||||
return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
||||
}; // eo function escape
|
||||
}
|
||||
|
||||
// create namespace
|
||||
Ext.ns('Ext.ux.form');
|
||||
|
||||
/**
|
||||
*
|
||||
* @class Ext.ux.form.LovCombo
|
||||
* @extends Ext.form.ComboBox
|
||||
*/
|
||||
Ext.ux.form.LovCombo = Ext.extend(Ext.form.ComboBox, {
|
||||
|
||||
// {{{
|
||||
// configuration options
|
||||
/**
|
||||
* @cfg {String} checkField name of field used to store checked state.
|
||||
* It is automatically added to existing fields.
|
||||
* Change it only if it collides with your normal field.
|
||||
*/
|
||||
checkField:'checked'
|
||||
|
||||
/**
|
||||
* @cfg {String} separator separator to use between values and texts
|
||||
*/
|
||||
,separator:','
|
||||
|
||||
/**
|
||||
* @cfg {String/Array} tpl Template for items.
|
||||
* Change it only if you know what you are doing.
|
||||
*/
|
||||
// }}}
|
||||
// {{{
|
||||
,initComponent:function() {
|
||||
|
||||
// template with checkbox
|
||||
if(!this.tpl) {
|
||||
this.tpl =
|
||||
'<tpl for=".">'
|
||||
+'<div class="x-combo-list-item">'
|
||||
+'<img src="' + Ext.BLANK_IMAGE_URL + '" '
|
||||
+'class="ux-lovcombo-icon ux-lovcombo-icon-'
|
||||
+'{[values.' + this.checkField + '?"checked":"unchecked"' + ']}">'
|
||||
+'<div class="ux-lovcombo-item-text">{' + (this.displayField || 'text' )+ '}</div>'
|
||||
+'</div>'
|
||||
+'</tpl>'
|
||||
;
|
||||
}
|
||||
|
||||
// call parent
|
||||
Ext.ux.form.LovCombo.superclass.initComponent.apply(this, arguments);
|
||||
|
||||
// install internal event handlers
|
||||
this.on({
|
||||
scope:this
|
||||
,beforequery:this.onBeforeQuery
|
||||
,blur:this.onRealBlur
|
||||
});
|
||||
|
||||
// remove selection from input field
|
||||
this.onLoad = this.onLoad.createSequence(function() {
|
||||
if(this.el) {
|
||||
var v = this.el.dom.value;
|
||||
this.el.dom.value = '';
|
||||
this.el.dom.value = v;
|
||||
}
|
||||
});
|
||||
|
||||
} // e/o function initComponent
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* Disables default tab key bahavior
|
||||
* @private
|
||||
*/
|
||||
,initEvents:function() {
|
||||
Ext.ux.form.LovCombo.superclass.initEvents.apply(this, arguments);
|
||||
|
||||
// disable default tab handling - does no good
|
||||
this.keyNav.tab = false;
|
||||
|
||||
} // eo function initEvents
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* clears value
|
||||
*/
|
||||
,clearValue:function() {
|
||||
this.value = '';
|
||||
this.setRawValue(this.value);
|
||||
this.store.clearFilter();
|
||||
this.store.each(function(r) {
|
||||
r.set(this.checkField, false);
|
||||
}, this);
|
||||
if(this.hiddenField) {
|
||||
this.hiddenField.value = '';
|
||||
}
|
||||
this.applyEmptyText();
|
||||
} // eo function clearValue
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* @return {String} separator (plus space) separated list of selected displayFields
|
||||
* @private
|
||||
*/
|
||||
,getCheckedDisplay:function() {
|
||||
var re = new RegExp(this.separator, "g");
|
||||
return this.getCheckedValue(this.displayField).replace(re, this.separator + ' ');
|
||||
} // eo function getCheckedDisplay
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* @return {String} separator separated list of selected valueFields
|
||||
* @private
|
||||
*/
|
||||
,getCheckedValue:function(field) {
|
||||
field = field || this.valueField;
|
||||
var c = [];
|
||||
|
||||
// store may be filtered so get all records
|
||||
var snapshot = this.store.snapshot || this.store.data;
|
||||
|
||||
snapshot.each(function(r) {
|
||||
if(r.get(this.checkField)) {
|
||||
c.push(r.get(field));
|
||||
}
|
||||
}, this);
|
||||
|
||||
return c.join(this.separator);
|
||||
} // eo function getCheckedValue
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* beforequery event handler - handles multiple selections
|
||||
* @param {Object} qe query event
|
||||
* @private
|
||||
*/
|
||||
,onBeforeQuery:function(qe) {
|
||||
qe.query = qe.query.replace(new RegExp(this.getCheckedDisplay() + '[ ' + this.separator + ']*'), '');
|
||||
} // eo function onBeforeQuery
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* blur event handler - runs only when real blur event is fired
|
||||
*/
|
||||
,onRealBlur:function() {
|
||||
this.list.hide();
|
||||
var rv = this.getRawValue();
|
||||
var rva = rv.split(new RegExp(RegExp.escape(this.separator) + ' *'));
|
||||
var va = [];
|
||||
var snapshot = this.store.snapshot || this.store.data;
|
||||
|
||||
// iterate through raw values and records and check/uncheck items
|
||||
Ext.each(rva, function(v) {
|
||||
snapshot.each(function(r) {
|
||||
if(v === r.get(this.displayField)) {
|
||||
va.push(r.get(this.valueField));
|
||||
}
|
||||
}, this);
|
||||
}, this);
|
||||
this.setValue(va.join(this.separator));
|
||||
this.store.clearFilter();
|
||||
} // eo function onRealBlur
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* Combo's onSelect override
|
||||
* @private
|
||||
* @param {Ext.data.Record} record record that has been selected in the list
|
||||
* @param {Number} index index of selected (clicked) record
|
||||
*/
|
||||
,onSelect:function(record, index) {
|
||||
if(this.fireEvent('beforeselect', this, record, index) !== false){
|
||||
|
||||
// toggle checked field
|
||||
record.set(this.checkField, !record.get(this.checkField));
|
||||
|
||||
// display full list
|
||||
if(this.store.isFiltered()) {
|
||||
this.doQuery(this.allQuery);
|
||||
}
|
||||
|
||||
// set (update) value and fire event
|
||||
this.setValue(this.getCheckedValue());
|
||||
this.fireEvent('select', this, record, index);
|
||||
}
|
||||
} // eo function onSelect
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* Sets the value of the LovCombo
|
||||
* @param {Mixed} v value
|
||||
*/
|
||||
,setValue:function(v) {
|
||||
if(v) {
|
||||
v = '' + v;
|
||||
if(this.valueField) {
|
||||
this.store.clearFilter();
|
||||
this.store.each(function(r) {
|
||||
var checked = !(!v.match(
|
||||
'(^|' + this.separator + ')' + RegExp.escape(r.get(this.valueField))
|
||||
+'(' + this.separator + '|$)'))
|
||||
;
|
||||
|
||||
r.set(this.checkField, checked);
|
||||
}, this);
|
||||
this.value = this.getCheckedValue();
|
||||
this.setRawValue(this.getCheckedDisplay());
|
||||
if(this.hiddenField) {
|
||||
this.hiddenField.value = this.value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.value = v;
|
||||
this.setRawValue(v);
|
||||
if(this.hiddenField) {
|
||||
this.hiddenField.value = v;
|
||||
}
|
||||
}
|
||||
if(this.el) {
|
||||
this.el.removeClass(this.emptyClass);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.clearValue();
|
||||
}
|
||||
} // eo function setValue
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* Selects all items
|
||||
*/
|
||||
,selectAll:function() {
|
||||
this.store.each(function(record){
|
||||
// toggle checked field
|
||||
record.set(this.checkField, true);
|
||||
}, this);
|
||||
|
||||
//display full list
|
||||
this.doQuery(this.allQuery);
|
||||
this.setValue(this.getCheckedValue());
|
||||
} // eo full selectAll
|
||||
// }}}
|
||||
// {{{
|
||||
/**
|
||||
* Deselects all items. Synonym for clearValue
|
||||
*/
|
||||
,deselectAll:function() {
|
||||
this.clearValue();
|
||||
} // eo full deselectAll
|
||||
// }}}
|
||||
|
||||
}); // eo extend
|
||||
|
||||
// register xtype
|
||||
Ext.reg('lovcombo', Ext.ux.form.LovCombo);
|
||||
|
|
|
@ -2,79 +2,11 @@ tvheadend.grabberStore = new Ext.data.JsonStore({
|
|||
root:'entries',
|
||||
fields: ['identifier','name','version','apiconfig'],
|
||||
url:'xmltv',
|
||||
baseParams: {op: 'listGrabbers'}
|
||||
baseParams: {
|
||||
op: 'listGrabbers'
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
tvheadend.xmltvsetup = function() {
|
||||
|
||||
var deck1info = new Ext.form.Label({
|
||||
fieldLabel: 'Version',
|
||||
html:'',
|
||||
});
|
||||
|
||||
var deck1cb = new Ext.form.ComboBox({
|
||||
loadingText: 'Scanning for XMLTV grabbers, please wait...',
|
||||
fieldLabel: 'XML-TV Source',
|
||||
name: 'xmltvchannel',
|
||||
width: 350,
|
||||
displayField:'name',
|
||||
valueField:'identifier',
|
||||
store: tvheadend.grabberStore,
|
||||
forceSelection: true,
|
||||
editable: false,
|
||||
triggerAction: 'all',
|
||||
mode: 'remote',
|
||||
emptyText: 'Select grabber'
|
||||
});
|
||||
|
||||
var deck1 = new Ext.FormPanel({
|
||||
labelAlign: 'right',
|
||||
labelWidth: 100,
|
||||
bodyStyle: 'padding: 5px',
|
||||
defaultType: 'label',
|
||||
layout: 'form',
|
||||
border:false,
|
||||
items: [deck1cb, deck1info]
|
||||
});
|
||||
|
||||
|
||||
|
||||
var win = new Ext.Window({
|
||||
title: 'Configure XMLTV grabbers',
|
||||
bodyStyle: 'padding: 5px',
|
||||
layout: 'fit',
|
||||
width: 500,
|
||||
height: 500,
|
||||
constrainHeader: true,
|
||||
buttonAlign: 'center',
|
||||
items: [deck1],
|
||||
bbar: [
|
||||
{
|
||||
id: 'move-back',
|
||||
text: 'Back',
|
||||
disabled: true
|
||||
},
|
||||
'->',
|
||||
{
|
||||
id: 'move-next',
|
||||
text: 'Next'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
win.show();
|
||||
|
||||
deck1cb.on('select', function(c,r,i) {
|
||||
deck1info.setText(r.data.version);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
tvheadend.xmltv = function() {
|
||||
|
||||
var confreader = new Ext.data.JsonReader({
|
||||
|
|
Loading…
Add table
Reference in a new issue