stremiok 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/README.md +9 -9
- package/config.xml +1 -1
- package/css/style.css +27 -0
- package/index.html +34 -31
- package/js/app.js +19 -7
- package/js/epg.js +320 -0
- package/js/iptv.js +198 -25
- package/js/keys.js +2 -0
- package/js/player-controls.js +50 -8
- package/js/player-core.js +112 -12
- package/js/player-store.js +21 -1
- package/js/player-subs.js +38 -8
- package/js/stremio.js +70 -6
- package/js/trakt.js +1 -0
- package/js/util.js +24 -13
- package/js/view-core.js +128 -55
- package/js/view-guide.js +332 -0
- package/js/view-home.js +148 -33
- package/js/view-settings.js +103 -35
- package/js/views.js +3 -2
- package/js/ytplay.js +53 -5
- package/package.json +2 -1
- package/service/index.js +863 -46
package/js/view-home.js
CHANGED
|
@@ -26,49 +26,116 @@
|
|
|
26
26
|
{ key: 'movie', icon: '🎬', label: 'Movies' },
|
|
27
27
|
{ key: 'series', icon: '📺', label: 'Series' },
|
|
28
28
|
{ key: 'tv', icon: '📡', label: 'IPTV' },
|
|
29
|
+
{ key: 'guide', icon: '☰', label: 'Guide' }, // (the IPTV channels' TV guide as a grid, once one is read: see js/view-guide.js)
|
|
29
30
|
{ key: 'radio', icon: '📻', label: 'Radio' },
|
|
30
31
|
{ key: 'sport', icon: '⚽', label: 'Sports' },
|
|
31
32
|
{ key: 'youtube', icon: '📹', label: 'YouTube' }, // (StremioK's YouTube catalogs, an addon's YouTube / channel ones: see isYouTubeCat)
|
|
32
33
|
{ key: 'local', icon: '💾', label: 'Local' } // (a USB drive plugged in: see LocalMedia)
|
|
33
34
|
];
|
|
34
35
|
function hasLocal() { return typeof LocalMedia !== 'undefined' && LocalMedia.present(); }
|
|
36
|
+
function hasGuide() { return typeof EPG !== 'undefined' && !!EPG.hasAny && EPG.hasAny(); }
|
|
37
|
+
|
|
38
|
+
// The top menu's order (Settings › Appearance › Menu): Watching first and Settings last, the
|
|
39
|
+
// rest (the tabs, Discover, Search) in yours. A saved order is kept as far as it goes: an
|
|
40
|
+
// item it doesn't have (one added since) goes after the one it usually follows, and one
|
|
41
|
+
// that no longer exists is dropped.
|
|
42
|
+
var MENU_ITEMS = HOME_TABS.filter(function (t) { return t.key !== 'continue'; }).map(function (t) { return t.key; }).concat(['discover', 'search']);
|
|
43
|
+
function menuOrder() {
|
|
44
|
+
var out = [];
|
|
45
|
+
(Stremio.getPrefs().menuOrder || []).forEach(function (k) { if (MENU_ITEMS.indexOf(k) !== -1 && out.indexOf(k) === -1) { out.push(k); } });
|
|
46
|
+
MENU_ITEMS.forEach(function (k, i) {
|
|
47
|
+
if (out.indexOf(k) !== -1) { return; }
|
|
48
|
+
var at = 0;
|
|
49
|
+
for (var j = i - 1; j >= 0; j--) { var p = out.indexOf(MENU_ITEMS[j]); if (p !== -1) { at = p + 1; break; } }
|
|
50
|
+
out.splice(at, 0, k);
|
|
51
|
+
});
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function moveMenuItem(key, dir) {
|
|
55
|
+
var o = menuOrder();
|
|
56
|
+
if (!U.moveIn(o, o.indexOf(key), dir)) { return false; }
|
|
57
|
+
Stremio.setPref('menuOrder', o);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// What the top menu shows, left to right, before Settings: Watching, then in your order the
|
|
61
|
+
// tabs showing (availableTabs), Discover and Search. [{ key, tab }] (tab: a Home tab's entry).
|
|
62
|
+
function menuItems() {
|
|
63
|
+
var tabs = availableTabs(), byKey = {}, out = [];
|
|
64
|
+
tabs.forEach(function (t) { byKey[t.key] = t; });
|
|
65
|
+
if (byKey['continue']) { out.push({ key: 'continue', tab: byKey['continue'] }); }
|
|
66
|
+
menuOrder().forEach(function (k) {
|
|
67
|
+
if (k === 'discover' || k === 'search') { out.push({ key: k }); }
|
|
68
|
+
else if (byKey[k]) { out.push({ key: k, tab: byKey[k] }); }
|
|
69
|
+
});
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
35
72
|
|
|
36
73
|
// Which tabs to show in the top menu (needs the home catalogs; falls back to the
|
|
37
74
|
// standard set before they've loaded so the menu is consistent everywhere).
|
|
38
|
-
// Tabs you hid (Settings › Appearance ›
|
|
75
|
+
// Tabs you hid (Settings › Appearance › Menu) aren't shown; Watching always is.
|
|
39
76
|
function availableTabs() {
|
|
40
77
|
var hidden = Stremio.getPrefs().hiddenTabs || [];
|
|
41
|
-
function shown(t) { return t.key === 'continue' || hidden.indexOf(t.key) === -1; }
|
|
78
|
+
function shown(t) { return t.key === 'continue' || (hidden.indexOf(t.key) === -1 && (t.key !== 'guide' || hasGuide())); }
|
|
42
79
|
if (_homeCatalogs === null || _homeFirstLoad) { // first load still running
|
|
43
80
|
return HOME_TABS.filter(function (t) { return shown(t) && (t.key !== 'local' || hasLocal()); });
|
|
44
81
|
}
|
|
45
82
|
return HOME_TABS.filter(function (t) {
|
|
46
|
-
return shown(t) && (t.key === 'continue' || t.key === 'calendar' || (t.key === 'local' ? hasLocal() : catsForTab(t.key).length > 0));
|
|
83
|
+
return shown(t) && (t.key === 'continue' || t.key === 'calendar' || t.key === 'guide' || (t.key === 'local' ? hasLocal() : catsForTab(t.key).length > 0));
|
|
47
84
|
});
|
|
48
85
|
}
|
|
49
86
|
|
|
50
87
|
// YouTube's tab: StremioK's own YouTube catalogs (whatever type you gave one: "Podcasts"),
|
|
51
88
|
// and another addon's YouTube or channel catalogs (YouTubio's "YouTube", Stremio's "channel").
|
|
89
|
+
// (Home's copies of a catalog, and the ones saved for the next launch, keep only its name,
|
|
90
|
+
// type and titles: a YouTube one carries yt, as its addon isn't kept with them)
|
|
52
91
|
function isYouTubeCat(c) {
|
|
53
92
|
var m = c.addon && c.addon.manifest;
|
|
54
|
-
return !!(m && m.id === 'org.stremiok.youtube') || /^(youtube|channel)$/i.test('' + c.type);
|
|
93
|
+
return !!c.yt || !!(m && m.id === 'org.stremiok.youtube') || /^(youtube|channel)$/i.test('' + c.type);
|
|
55
94
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
function copyCat(c, metas, more) { // (name, type, titles: and yt when it's one)
|
|
96
|
+
var o = { name: c.name, type: c.type, metas: metas };
|
|
97
|
+
if (c.yt) { o.yt = true; }
|
|
98
|
+
if (more) { Object.keys(more).forEach(function (k) { o[k] = more[k]; }); }
|
|
99
|
+
return o;
|
|
100
|
+
}
|
|
101
|
+
// Which cached catalogs belong to a tab (tabOf). Movies, series, sports, radio and YouTube go
|
|
102
|
+
// to their own tabs, live TV (LIVE_TYPES, IPTV playlists) to IPTV. Another type (a debrid
|
|
103
|
+
// library's "RealDebrid", "other") goes by what it lists (byItems); anything still unclear
|
|
104
|
+
// isn't on Home ('': Discover has it). ("anime" never gets here: see Stremio's discoverOnly.)
|
|
105
|
+
var LIVE_TYPES = { tv: 1, events: 1, event: 1, live: 1, livetv: 1, iptv: 1 };
|
|
106
|
+
var TITLE_IDS = /^(tt\d+|tmdb:|tvdb:|kitsu:|mal:|anilist:|anidb:)/i;
|
|
107
|
+
function tabOf(c) {
|
|
108
|
+
var n = (c.metas || []).length;
|
|
109
|
+
if (c._tab !== undefined && c._tabN === n) { return c._tab; } // (worked out again once it has titles)
|
|
110
|
+
var t = ('' + (c.type || '')).toLowerCase(), tab;
|
|
111
|
+
if (isYouTubeCat(c)) { tab = 'youtube'; }
|
|
112
|
+
else if (t === 'movie' || t === 'series' || t === 'radio') { tab = t; }
|
|
113
|
+
else if (t === 'sport' || t === 'sports') { tab = 'sport'; }
|
|
114
|
+
else if (LIVE_TYPES[t]) { tab = 'tv'; }
|
|
115
|
+
else { tab = byItems(c); }
|
|
116
|
+
c._tab = tab; c._tabN = n;
|
|
117
|
+
return tab;
|
|
118
|
+
}
|
|
119
|
+
// Mostly films or series (60% of its first 50 titles): that tab. Title ids, and "anime",
|
|
120
|
+
// "series" or "show" in its name or type: Series ("movie" / "film": Movies). Else ''.
|
|
121
|
+
function byItems(c) {
|
|
122
|
+
var ms = (c.metas || []).slice(0, 50), mv = 0, sr = 0, ids = 0;
|
|
123
|
+
if (!ms.length) { return ''; }
|
|
124
|
+
ms.forEach(function (m) {
|
|
125
|
+
var mt = ('' + ((m && m.type) || '')).toLowerCase();
|
|
126
|
+
if (mt === 'movie') { mv++; } else if (mt === 'series') { sr++; }
|
|
127
|
+
if (TITLE_IDS.test('' + ((m && m.id) || ''))) { ids++; }
|
|
71
128
|
});
|
|
129
|
+
if (mv + sr >= ms.length * 0.6) { return mv > sr ? 'movie' : 'series'; }
|
|
130
|
+
if (ids >= ms.length * 0.6) {
|
|
131
|
+
var hint = (c.type + ' ' + (c.name || '') + ' ' + ((c.def && c.def.name) || '')).toLowerCase();
|
|
132
|
+
if (/movie|film|pel[ií]cula/.test(hint)) { return 'movie'; }
|
|
133
|
+
if (/anime|series|show|serie/.test(hint)) { return 'series'; }
|
|
134
|
+
}
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
function catsForTab(key) {
|
|
138
|
+
return (_homeCatalogs || []).filter(function (c) { return tabOf(c) === key; });
|
|
72
139
|
}
|
|
73
140
|
var _homeCatalogs = null; // [{name,type,metas}] — fetched once per session, filtered per tab
|
|
74
141
|
var _homeTab = null; // active tab key
|
|
@@ -98,8 +165,11 @@
|
|
|
98
165
|
// url: the addon ('local:iptv' for IPTV); none: nothing to fetch (only the set / order
|
|
99
166
|
// of rows changed). Calls before the reload runs add up.
|
|
100
167
|
var _reloadOnly = null; // { addon url: 1 } for the next reload, else a full one
|
|
168
|
+
var _homeAddons = {}; // { addon url: 1 } the addons Home's last load had catalogs from
|
|
101
169
|
function refreshAddonRows(url) {
|
|
102
170
|
if (_homeCatalogs === null) { clearCatalogCache(); return; } // nothing loaded yet: a full load anyway
|
|
171
|
+
// an addon Home didn't load rows from (the first playlist added: IPTV): a full load, its tab with it
|
|
172
|
+
if (url && !_homeAddons[url]) { clearCatalogCache(); return; }
|
|
103
173
|
_reloadOnly = _reloadOnly || {};
|
|
104
174
|
if (url) { _reloadOnly[url] = 1; }
|
|
105
175
|
_homeDirty = true;
|
|
@@ -148,17 +218,17 @@
|
|
|
148
218
|
if (bytes > _homeBytesMax) { return; }
|
|
149
219
|
var metas = c.metas.map(trimMeta);
|
|
150
220
|
metas.forEach(function (m) { bytes += sizeOf(m); });
|
|
151
|
-
if (bytes <= _homeBytesMax) { cats.push(
|
|
221
|
+
if (bytes <= _homeBytesMax) { cats.push(copyCat(c, metas)); }
|
|
152
222
|
});
|
|
153
223
|
if (U.bigStore && !_homeSaveFailed) {
|
|
154
224
|
U.bigStore.set(HOME_CACHE_KEY, { sig: sig, cats: cats }).then(function (ok) { if (ok === false) { _homeSaveFailed = true; } });
|
|
155
225
|
}
|
|
156
226
|
U.store.set(HOME_SNAP_KEY, { sig: sig, cats: cats.slice(0, SNAP_ROWS).map(function (c) {
|
|
157
|
-
return
|
|
227
|
+
return copyCat(c, c.metas.slice(0, SNAP_CARDS).map(function (m) {
|
|
158
228
|
var o = { id: m.id, type: m.type, name: m.name };
|
|
159
229
|
SNAP_FIELDS.forEach(function (k) { if (m[k] != null) { o[k] = m[k]; } });
|
|
160
230
|
return o;
|
|
161
|
-
})
|
|
231
|
+
}));
|
|
162
232
|
}) });
|
|
163
233
|
}
|
|
164
234
|
function useHomeCache() {
|
|
@@ -1150,7 +1220,7 @@
|
|
|
1150
1220
|
// Rows for the active tab, from the once-per-session catalog cache. Catalog tabs
|
|
1151
1221
|
// keep their built rows, so switching back doesn't rebuild every card; Continue
|
|
1152
1222
|
// Watching is small and changes after every playback, so it's always rebuilt.
|
|
1153
|
-
var rows = (_homeTab !== 'continue' && _homeTab !== 'local' && _homeTab !== 'calendar') ? _tabRows[_homeTab] : null;
|
|
1223
|
+
var rows = (_homeTab !== 'continue' && _homeTab !== 'local' && _homeTab !== 'calendar' && _homeTab !== 'guide') ? _tabRows[_homeTab] : null;
|
|
1154
1224
|
if (_homeTab !== 'calendar' && _calCancel) { _calCancel.cancel(); _calCancel = null; } // (its titles' details still coming: not wanted)
|
|
1155
1225
|
if (rows) {
|
|
1156
1226
|
s.appendChild(rows);
|
|
@@ -1162,6 +1232,8 @@
|
|
|
1162
1232
|
localRows(rows);
|
|
1163
1233
|
} else if (_homeTab === 'calendar') {
|
|
1164
1234
|
calendarRows(rows);
|
|
1235
|
+
} else if (_homeTab === 'guide') {
|
|
1236
|
+
V.guideGrid(rows);
|
|
1165
1237
|
} else if (_homeTab === 'continue') {
|
|
1166
1238
|
var entries = continueEntries();
|
|
1167
1239
|
if (entries.length) {
|
|
@@ -1194,12 +1266,15 @@
|
|
|
1194
1266
|
var again = keep ? findCard(s, keep) : null;
|
|
1195
1267
|
var landed = false;
|
|
1196
1268
|
var sameItem = (!firstCard && inMenu) ? menuItem(s, cur.textContent) : null; // the menu item focus was on
|
|
1197
|
-
|
|
1269
|
+
var rowsEl = s.querySelector('.rows'), fc = Focus.current();
|
|
1270
|
+
if (_homeTab === 'guide' && fc && rowsEl && rowsEl.contains(fc)) { landed = true; } // (the Guide drawn from what it had put focus back on its program)
|
|
1271
|
+
else if (again || sameItem) { Focus.setScope(s, again || sameItem); } else { landed = focusHome(s, !firstCard && inMenu); }
|
|
1198
1272
|
if (_homeCatalogs === null || homeStale() || _reloadOnly) { loadHomeCatalogs(); }
|
|
1199
1273
|
// No title to focus yet: try again when the rows arrive (Continue Watching and a
|
|
1200
1274
|
// tab with no catalogs have nothing coming, so focus simply stays on the pill).
|
|
1201
|
-
_focusFirstCard = (firstCard && !landed && _homeLoading && _homeTab !== 'continue' && _homeTab !== 'calendar') ? 'pending' : false;
|
|
1275
|
+
_focusFirstCard = (firstCard && !landed && _homeLoading && _homeTab !== 'continue' && _homeTab !== 'calendar' && _homeTab !== 'guide') ? 'pending' : false;
|
|
1202
1276
|
_renderedSig = tabSig();
|
|
1277
|
+
_renderedMenu = menuSig();
|
|
1203
1278
|
if (s.querySelector('.rows .card')) { U.mark('firstCard'); }
|
|
1204
1279
|
// the strip describes the focused title only (empty while focus is in the menu)
|
|
1205
1280
|
clearTimeout(_homeInfoTimer); _homeInfoTimer = null;
|
|
@@ -1214,6 +1289,7 @@
|
|
|
1214
1289
|
function fillInfoBox(box, m) {
|
|
1215
1290
|
if (!box) { return; }
|
|
1216
1291
|
m = m || {};
|
|
1292
|
+
if (!m.logo && /^iptv:/.test(m.id || '') && typeof EPG !== 'undefined' && EPG.logo && EPG.logo(m.id)) { m = Object.assign({}, m, { logo: EPG.logo(m.id) }); } // (a channel: its guide's logo)
|
|
1217
1293
|
var bits = [];
|
|
1218
1294
|
if (m.releaseInfo) { bits.push(m.releaseInfo); } else if (m.year) { bits.push('' + m.year); }
|
|
1219
1295
|
if (m.runtime) { bits.push(m.runtime); }
|
|
@@ -1231,8 +1307,34 @@
|
|
|
1231
1307
|
} else {
|
|
1232
1308
|
t.textContent = name;
|
|
1233
1309
|
}
|
|
1310
|
+
var desc = m.description || '';
|
|
1311
|
+
var g = /^iptv:/.test(m.id || '') && typeof EPG !== 'undefined' ? EPG.now(m.id) : null; // a channel: what's on, from its TV guide
|
|
1312
|
+
if (g && g.now) {
|
|
1313
|
+
bits = ['Now ' + EPG.hhmm(g.now.start) + '–' + EPG.hhmm(g.now.stop) + ' ' + g.now.title];
|
|
1314
|
+
desc = (g.now.desc ? g.now.desc + ' ' : '') + (g.next ? 'Next ' + EPG.hhmm(g.next.start) + ' ' + g.next.title : '');
|
|
1315
|
+
}
|
|
1234
1316
|
box.children[1].textContent = bits.join(' · ');
|
|
1235
|
-
box.children[2].textContent =
|
|
1317
|
+
box.children[2].textContent = desc;
|
|
1318
|
+
// a program's picture from the TV guide (the Guide tab's focused one, a channel's now)
|
|
1319
|
+
var picSrc = m.picture || (g && g.now && g.now.picture) || '', pic = box.querySelector('.home-info-pic');
|
|
1320
|
+
if (!picSrc) { if (pic) { pic.className = 'home-info-pic hidden'; } box.classList.remove('has-pic'); return; }
|
|
1321
|
+
if (!pic) { pic = U.el('img', 'home-info-pic'); pic.onerror = function () { pic.className = 'home-info-pic hidden'; box.classList.remove('has-pic'); }; box.appendChild(pic); }
|
|
1322
|
+
pic.className = 'home-info-pic';
|
|
1323
|
+
box.classList.add('has-pic');
|
|
1324
|
+
if (pic.__src !== picSrc) { pic.__src = picSrc; U.deferImage(pic, U.cardImage(picSrc, 'still'), true); }
|
|
1325
|
+
}
|
|
1326
|
+
if (typeof EPG !== 'undefined') { // (the focused channel's: drawn again when its guide arrives)
|
|
1327
|
+
var _hadGuide = false;
|
|
1328
|
+
EPG.onChange(function () {
|
|
1329
|
+
var n = Focus.current();
|
|
1330
|
+
if (n && n.__meta && /^iptv:/.test(n.__meta.id || '') && scr('home').contains(n)) { fillHomeInfo(n.__meta); }
|
|
1331
|
+
// a guide read (or gone): the Guide tab comes (or goes) in the menu, at once
|
|
1332
|
+
var has = hasGuide();
|
|
1333
|
+
if (has !== _hadGuide) {
|
|
1334
|
+
_hadGuide = has;
|
|
1335
|
+
if (_current() === scr('home') && !isModalOpen() && !Player.isActive()) { renderHome(); }
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1236
1338
|
}
|
|
1237
1339
|
Focus.onFocus(function (node) {
|
|
1238
1340
|
if (!node || !scr('home').contains(node)) { return; }
|
|
@@ -1263,12 +1365,18 @@
|
|
|
1263
1365
|
// Everything the tab on screen shows from the catalogs (which titles, in which rows),
|
|
1264
1366
|
// to tell whether a refresh changed it. Continue Watching shows none of it.
|
|
1265
1367
|
function shownSig() {
|
|
1266
|
-
if (_homeTab === 'continue' || _homeTab === 'calendar') { return _homeTab; }
|
|
1368
|
+
if (_homeTab === 'continue' || _homeTab === 'calendar' || _homeTab === 'guide') { return _homeTab; }
|
|
1267
1369
|
return _homeTab + '#' + catsForTab(_homeTab).map(function (c) {
|
|
1268
1370
|
return c.name + ':' + c.metas.map(function (m) { return m.id + '/' + (m.poster || ''); }).join(',');
|
|
1269
1371
|
}).join('|');
|
|
1270
1372
|
}
|
|
1271
|
-
var _renderedSig = '';
|
|
1373
|
+
var _renderedSig = '', _renderedMenu = '';
|
|
1374
|
+
// The tabs that aren't drawn from catalogs (Watching, Calendar, Guide, Local): catalogs that
|
|
1375
|
+
// arrive don't change them, only the top menu might (a tab coming). Redrawing one rebuilds it
|
|
1376
|
+
// whole (the Guide asks for its programs again): a flicker, so not unless the menu changed.
|
|
1377
|
+
function ownTab(t) { return t === 'continue' || t === 'calendar' || t === 'guide' || t === 'local'; }
|
|
1378
|
+
function menuSig() { return availableTabs().map(function (t) { return t.key; }).join(','); }
|
|
1379
|
+
function ownTabStill() { return ownTab(_homeTab) && menuSig() === _renderedMenu; }
|
|
1272
1380
|
function tabSig() {
|
|
1273
1381
|
return _homeTab + '#' + catsForTab(_homeTab).map(function (c) { return c.name + ':' + c.metas.length; }).join('|');
|
|
1274
1382
|
}
|
|
@@ -1312,7 +1420,9 @@
|
|
|
1312
1420
|
}
|
|
1313
1421
|
function onHome() { return _current() === s && !isModalOpen() && !Player.isActive(); }
|
|
1314
1422
|
Stremio.catalogsForHome().then(function (cats) {
|
|
1315
|
-
|
|
1423
|
+
_homeAddons = {};
|
|
1424
|
+
cats.forEach(function (cat) { if (cat.addon && cat.addon.url) { _homeAddons[cat.addon.url] = 1; } });
|
|
1425
|
+
var acc = cats.map(function (cat) { return copyCat({ name: cat.name, type: cat.type, yt: isYouTubeCat(cat) }, []); });
|
|
1316
1426
|
var arrived = cats.map(function () { return false; }), shown = 0, partialTimer = null;
|
|
1317
1427
|
// First load: publish each row as soon as it arrives, in its own place (a slower one above
|
|
1318
1428
|
// it held by a "Loading…" placeholder, so nothing jumps when it comes), and redraw when
|
|
@@ -1321,12 +1431,13 @@
|
|
|
1321
1431
|
var n = arrived.filter(Boolean).length, last = arrived.lastIndexOf(true);
|
|
1322
1432
|
if (!first || n <= shown) { return; }
|
|
1323
1433
|
shown = n;
|
|
1324
|
-
var now = acc.slice(0, last + 1).map(function (c, i) { return arrived[i] ? c :
|
|
1434
|
+
var now = acc.slice(0, last + 1).map(function (c, i) { return arrived[i] ? c : copyCat(c, [], { loading: true }); });
|
|
1325
1435
|
_homeCatalogs = splitOtherCatalogs(now).filter(function (c) { return c.loading || (c.metas && c.metas.length); });
|
|
1326
1436
|
if (partialTimer) { return; }
|
|
1327
1437
|
partialTimer = setTimeout(function () {
|
|
1328
1438
|
partialTimer = null;
|
|
1329
1439
|
if (!_homeFirstLoad) { return; } // everything arrived meanwhile: drawn below
|
|
1440
|
+
if (ownTabStill()) { _tabRows = {}; return; } // (Watching, the Guide…: as they are)
|
|
1330
1441
|
var keep = {};
|
|
1331
1442
|
if (tabSig() === _renderedSig) { keep[_homeTab] = _tabRows[_homeTab]; }
|
|
1332
1443
|
_tabRows = keep; // other tabs rebuild from the new rows when opened
|
|
@@ -1351,7 +1462,7 @@
|
|
|
1351
1462
|
// The tab on screen is redrawn only if what it shows changed: redrawing rebuilds
|
|
1352
1463
|
// every card (and reloads its artwork), which shows as a flicker. Continue
|
|
1353
1464
|
// Watching doesn't come from catalogs at all. Other tabs rebuild when opened.
|
|
1354
|
-
var same = before !== null && shownSig() === before;
|
|
1465
|
+
var same = (before !== null && shownSig() === before) || ownTabStill();
|
|
1355
1466
|
var keep = {};
|
|
1356
1467
|
if (same && _tabRows[_homeTab]) { keep[_homeTab] = _tabRows[_homeTab]; }
|
|
1357
1468
|
_tabRows = keep;
|
|
@@ -1375,8 +1486,8 @@
|
|
|
1375
1486
|
function splitOtherCatalogs(list) {
|
|
1376
1487
|
var out = [];
|
|
1377
1488
|
list.forEach(function (c) {
|
|
1378
|
-
if (c.loading) { if (c.type !== 'other') { out.push(
|
|
1379
|
-
if (c.type !== 'other') { out.push(
|
|
1489
|
+
if (c.loading) { if (c.type !== 'other' || c.yt) { out.push(copyCat(c, [], { loading: true })); } return; } // (a placeholder: still loading)
|
|
1490
|
+
if (c.type !== 'other' || c.yt) { out.push(copyCat(c, c.metas.slice(0, ROW_CARDS))); return; } // (a YouTube one as it is, whatever its type)
|
|
1380
1491
|
var movies = [], series = [];
|
|
1381
1492
|
c.metas.forEach(function (m) { (SERIES_NAME.test(m.name || '') ? series : movies).push(m); });
|
|
1382
1493
|
out.push({ name: otherRowName('Movies', c.name), type: 'movie', metas: movies.slice(0, ROW_CARDS) });
|
|
@@ -1435,6 +1546,10 @@
|
|
|
1435
1546
|
// ----- published for the other parts (and js/views.js) -----
|
|
1436
1547
|
V.fillInfoBox = fillInfoBox;
|
|
1437
1548
|
V.availableTabs = availableTabs;
|
|
1549
|
+
V.menuOrder = menuOrder;
|
|
1550
|
+
V.moveMenuItem = moveMenuItem;
|
|
1551
|
+
V.menuItems = menuItems;
|
|
1552
|
+
V.MENU_ITEMS = MENU_ITEMS;
|
|
1438
1553
|
V.HOME_TABS = HOME_TABS;
|
|
1439
1554
|
V.buildRow = buildRow;
|
|
1440
1555
|
V.clearCatalogCache = clearCatalogCache;
|
package/js/view-settings.js
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
function markHomeDirty() { return V.markHomeDirty.apply(null, arguments); } // home
|
|
18
18
|
function onModalClose() { return V.onModalClose.apply(null, arguments); } // core
|
|
19
19
|
function pickList() { return V.pickList.apply(null, arguments); } // core
|
|
20
|
-
function toggleList() { return V.toggleList.apply(null, arguments); } // core
|
|
21
20
|
function playlistActions() { return V.playlistActions.apply(null, arguments); } // core
|
|
22
21
|
function scr() { return V.scr.apply(null, arguments); } // core
|
|
23
22
|
function showModal() { return V.showModal.apply(null, arguments); } // core
|
|
@@ -207,14 +206,16 @@
|
|
|
207
206
|
get: function () { return { spoilers: Stremio.getPrefs().spoilers }; },
|
|
208
207
|
set: function (cfg) { Stremio.setPref('spoilers', cfg.spoilers); }
|
|
209
208
|
}));
|
|
210
|
-
|
|
209
|
+
var menu = sectionRow('Menu ›', 'Order the top menu, and show or hide its tabs', 'menuorder');
|
|
210
|
+
menu.className += ' cfg-row';
|
|
211
|
+
wrap.appendChild(menu);
|
|
211
212
|
var feeds = sectionRow('Home feeds ›', 'Order the Home rows, and show or hide each one', 'feeds');
|
|
212
213
|
feeds.className += ' cfg-row';
|
|
213
214
|
wrap.appendChild(feeds);
|
|
214
215
|
var sfeeds = sectionRow('Search results ›', 'Order the Search rows (one per catalog that can search), and show or hide each one', 'searchfeeds');
|
|
215
216
|
sfeeds.className += ' cfg-row';
|
|
216
217
|
wrap.appendChild(sfeeds);
|
|
217
|
-
var back = _focusChild === 'feeds' ? feeds : _focusChild === 'searchfeeds' ? sfeeds : null;
|
|
218
|
+
var back = _focusChild === 'menuorder' ? menu : _focusChild === 'feeds' ? feeds : _focusChild === 'searchfeeds' ? sfeeds : null;
|
|
218
219
|
_focusChild = null;
|
|
219
220
|
if (back) { Focus.setScope(scr('settings'), back); } else { focusSettings(); }
|
|
220
221
|
}
|
|
@@ -337,7 +338,7 @@
|
|
|
337
338
|
// BACK goes up one level, onto the row it came from: from a section to its group (on
|
|
338
339
|
// that section, not the first one), from Home feeds / Search results to Appearance,
|
|
339
340
|
// from a group to the Settings list (on that group).
|
|
340
|
-
var SECTION_PARENT = { feeds: 'appearance', searchfeeds: 'appearance', aiomanager: 'addons' };
|
|
341
|
+
var SECTION_PARENT = { menuorder: 'appearance', feeds: 'appearance', searchfeeds: 'appearance', aiomanager: 'addons' };
|
|
341
342
|
Object.keys(SETTINGS_GROUPS).forEach(function (g) {
|
|
342
343
|
SETTINGS_GROUPS[g].rows.forEach(function (row) { SECTION_PARENT[row[2]] = g; });
|
|
343
344
|
});
|
|
@@ -366,6 +367,7 @@
|
|
|
366
367
|
else if (_settingsSection === 'subtitles') { settingsSubtitles(wrap); }
|
|
367
368
|
else if (_settingsSection === 'skipping') { settingsSkipping(wrap); }
|
|
368
369
|
else if (_settingsSection === 'seeking') { settingsSeeking(wrap); }
|
|
370
|
+
else if (_settingsSection === 'menuorder') { settingsFeeds(wrap, 'menu'); }
|
|
369
371
|
else if (_settingsSection === 'feeds') { settingsFeeds(wrap, 'home'); }
|
|
370
372
|
else if (_settingsSection === 'searchfeeds') { settingsFeeds(wrap, 'search'); }
|
|
371
373
|
else if (_settingsSection === 'ranking') { settingsFeeds(wrap, 'ranking'); }
|
|
@@ -867,6 +869,15 @@
|
|
|
867
869
|
var _iptvFocus = null; // { id, ctl } to focus once the page is drawn again
|
|
868
870
|
function two(n) { return (n < 10 ? '0' : '') + n; }
|
|
869
871
|
function thousands(n) { return ('' + n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
|
|
872
|
+
// The playlist rows IPTV Settings drew last: their guide lines kept current by one listener
|
|
873
|
+
// (one added each time it was drawn used to pile up, each holding its rows).
|
|
874
|
+
var _guideRows = [];
|
|
875
|
+
if (typeof EPG !== 'undefined' && EPG.onChange) {
|
|
876
|
+
EPG.onChange(function () {
|
|
877
|
+
_guideRows = _guideRows.filter(function (r) { return document.documentElement.contains(r.info); });
|
|
878
|
+
_guideRows.forEach(function (r) { r.info.textContent = playlistInfo(r.p); });
|
|
879
|
+
});
|
|
880
|
+
}
|
|
870
881
|
function playlistInfo(p) {
|
|
871
882
|
if (p.off) { return 'Off — its channels aren’t shown'; }
|
|
872
883
|
var st = Stremio.iptvStatus(p.id), parts = [];
|
|
@@ -875,13 +886,65 @@
|
|
|
875
886
|
if (st.fetchedAt) { var d = new Date(st.fetchedAt); parts.push('downloaded ' + U.dayLabel(st.fetchedAt) + ' ' + two(d.getHours()) + ':' + two(d.getMinutes())); }
|
|
876
887
|
if (st.dropped) { parts.push(thousands(st.dropped) + ' more left out (a playlist keeps its first 30,000)'); }
|
|
877
888
|
if (st.uncached) { parts.push('too large to save on the TV: downloaded each launch'); }
|
|
889
|
+
var g = guideInfo(p);
|
|
890
|
+
if (g) { parts.push(g); }
|
|
878
891
|
return parts.join(' · ');
|
|
879
892
|
}
|
|
893
|
+
// Its TV guide (js/epg.js): being read, read (for how many of its channels), or why not.
|
|
894
|
+
function guideInfo(p) {
|
|
895
|
+
var g = typeof EPG !== 'undefined' && EPG.status ? EPG.status(p.id) : null;
|
|
896
|
+
if (!g) { return typeof EPG !== 'undefined' && EPG.available && EPG.available() ? 'no TV guide' : ''; }
|
|
897
|
+
if (g.state === 'ready') {
|
|
898
|
+
var d = new Date(g.at);
|
|
899
|
+
return 'TV guide: ' + thousands(g.channels) + ' channel' + (g.channels === 1 ? '' : 's') + ', ' + thousands(g.programmes) + ' programs' + (g.partial ? ' (part of it: too large)' : '') +
|
|
900
|
+
(g.at ? ', read ' + U.dayLabel(g.at) + ' ' + two(d.getHours()) + ':' + two(d.getMinutes()) : '') + (g.error ? ' (the last read failed: ' + g.error + ')' : '');
|
|
901
|
+
}
|
|
902
|
+
if (g.state === 'failed') { return '⚠ TV guide: ' + (g.error || 'couldn’t be read'); }
|
|
903
|
+
return 'TV guide: reading…';
|
|
904
|
+
}
|
|
880
905
|
function playlistProblem(p) {
|
|
881
906
|
var st = Stremio.iptvStatus(p.id);
|
|
882
907
|
if (p.off || !st.error) { return ''; }
|
|
883
908
|
return '⚠ ' + (st.stale ? 'Couldn’t refresh it (showing the saved copy): ' : '') + st.error;
|
|
884
909
|
}
|
|
910
|
+
// + Add Xtream account: its server, username and password, a page each (a whole Xtream link
|
|
911
|
+
// in the server's fills the rest); checked with the server, then added (Stremio.iptvAddXtream).
|
|
912
|
+
function addXtream() {
|
|
913
|
+
function done(base, user, pass) {
|
|
914
|
+
U.toast('Checking the account…', 15000);
|
|
915
|
+
IPTV.xtreamCheck(base, user, pass).then(function (r) {
|
|
916
|
+
if (r.unreachable) { U.toast('Couldn’t reach the server: check its address (nothing was saved)', 5000); return; }
|
|
917
|
+
if (r.refused) { U.toast('The server refused that username and password' + (r.status ? ' (' + r.status + ')' : '') + ': nothing was saved', 5000); return; }
|
|
918
|
+
Stremio.iptvAddXtream(base, user, pass);
|
|
919
|
+
refreshAddonRows('local:iptv');
|
|
920
|
+
U.toast('Xtream account added: reading its channels and TV guide…', 4000);
|
|
921
|
+
renderSettings();
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
App.openEntry({
|
|
925
|
+
title: 'Xtream account: server', phone: 'playlist',
|
|
926
|
+
hint: 'The server address your provider gave you (http://line.example.com:8080), or a whole Xtream link. RETURN cancels.',
|
|
927
|
+
value: 'http://', placeholder: 'http://line.example.com:8080',
|
|
928
|
+
validate: function (v) { return IPTV.xtreamParts(v) ? null : 'That isn’t a server address'; },
|
|
929
|
+
onConfirm: function (v) {
|
|
930
|
+
var x = IPTV.xtreamParts(v);
|
|
931
|
+
App.back();
|
|
932
|
+
if (x.user && x.pass) { done(x.base, x.user, x.pass); return; }
|
|
933
|
+
App.openEntry({
|
|
934
|
+
title: 'Xtream account: username', hint: 'Your account’s username. RETURN cancels.', value: x.user, placeholder: 'username',
|
|
935
|
+
validate: function (u) { return ('' + u).trim() ? null : 'Type the username'; },
|
|
936
|
+
onConfirm: function (u) {
|
|
937
|
+
App.back();
|
|
938
|
+
App.openEntry({
|
|
939
|
+
title: 'Xtream account: password', hint: 'Your account’s password (kept on the TV, in the playlist’s link). RETURN cancels.', value: '', placeholder: 'password', secret: true,
|
|
940
|
+
validate: function (p) { return ('' + p) ? null : 'Type the password'; },
|
|
941
|
+
onConfirm: function (p) { App.back(); done(x.base, ('' + u).trim(), '' + p); }
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
885
948
|
function settingsIptv(wrap) {
|
|
886
949
|
wrap.appendChild(U.el('h2', null, 'IPTV'));
|
|
887
950
|
var playlists = Stremio.iptvGetPlaylists ? Stremio.iptvGetPlaylists() : [];
|
|
@@ -925,6 +988,7 @@
|
|
|
925
988
|
rows.push({ p: playlist, info: info, problem: problem, idx: idx });
|
|
926
989
|
});
|
|
927
990
|
|
|
991
|
+
_guideRows = rows; // (their guide lines kept current as guides are read: _guideRows)
|
|
928
992
|
if (statusEl) {
|
|
929
993
|
Stremio.iptvLoad().then(function () {
|
|
930
994
|
var channels = 0, groups = 0, failed = 0, on = 0;
|
|
@@ -962,6 +1026,11 @@
|
|
|
962
1026
|
});
|
|
963
1027
|
};
|
|
964
1028
|
wrap.appendChild(add);
|
|
1029
|
+
var xt = U.el('div', 'addon-item focusable add-row');
|
|
1030
|
+
xt.appendChild(U.el('div', 'a-name', '+ Add Xtream account'));
|
|
1031
|
+
xt.appendChild(U.el('div', 'a-url', 'Its server, username and password: its channels and TV guide added'));
|
|
1032
|
+
xt.__onselect = addXtream;
|
|
1033
|
+
wrap.appendChild(xt);
|
|
965
1034
|
|
|
966
1035
|
wrap.appendChild(U.el('div', 'hint',
|
|
967
1036
|
'Channels are grouped into IPTV and Radio rows on Home. ▲ / ▼ set the playlists\' order: a channel in two playlists comes from the first.'));
|
|
@@ -1386,34 +1455,6 @@
|
|
|
1386
1455
|
focusSettings();
|
|
1387
1456
|
}
|
|
1388
1457
|
|
|
1389
|
-
// Home tabs: hide the ones you never use (Watching always stays: it's where Home opens). A
|
|
1390
|
-
// row like the ones above it; OK opens the tabs, OK on one shows or hides it (the list
|
|
1391
|
-
// stays open). A hidden tab's rows are back when it's shown again.
|
|
1392
|
-
function homeTabsRow() {
|
|
1393
|
-
function hiddenNow() { return Stremio.getPrefs().hiddenTabs || []; }
|
|
1394
|
-
var r = U.el('div', 'addon-item focusable cfg-row'), name = U.el('div', 'a-name');
|
|
1395
|
-
var tabs = (V.HOME_TABS || []).filter(function (t) { return t.key !== 'continue'; });
|
|
1396
|
-
function draw() {
|
|
1397
|
-
var hidden = hiddenNow().filter(function (k) { return tabs.some(function (t) { return t.key === k; }); });
|
|
1398
|
-
name.textContent = 'Home tabs: ' + (hidden.length ? hidden.length + ' hidden' : 'All shown');
|
|
1399
|
-
}
|
|
1400
|
-
draw();
|
|
1401
|
-
r.appendChild(name);
|
|
1402
|
-
r.appendChild(U.el('div', 'a-url', 'Press OK to choose'));
|
|
1403
|
-
r.__onselect = function () {
|
|
1404
|
-
var hidden = hiddenNow();
|
|
1405
|
-
toggleList('Home tabs', tabs.map(function (t) { return [t.key, t.label + (t.key === 'local' ? ' (with a USB drive in)' : ''), hidden.indexOf(t.key) === -1]; }),
|
|
1406
|
-
{ on: 'Shown', off: 'Hidden' }, function (key, on) {
|
|
1407
|
-
var now = hiddenNow().filter(function (k) { return k !== key; });
|
|
1408
|
-
if (!on) { now.push(key); }
|
|
1409
|
-
Stremio.setPref('hiddenTabs', now);
|
|
1410
|
-
markHomeDirty();
|
|
1411
|
-
draw();
|
|
1412
|
-
});
|
|
1413
|
-
};
|
|
1414
|
-
return r;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
1458
|
var POSTER_SHAPES = [['portrait', 'Portrait'], ['landscape', 'Landscape']];
|
|
1418
1459
|
// ----- Diagnostics › Logs: the recent problems U.log kept (in memory, URLs masked), and
|
|
1419
1460
|
// how long launches took -----
|
|
@@ -1630,14 +1671,17 @@
|
|
|
1630
1671
|
if (!inf.entries.length) { line(dbOn ? 'Nothing in the TV’s database.' : 'The TV’s database isn’t used (turned off above).'); }
|
|
1631
1672
|
line('Settings storage: ' + fmtBytes(inf.ls) + ' (progress, watched marks, settings…)' + (inf.lsTop.length ? '; the largest:' : ''));
|
|
1632
1673
|
inf.lsTop.forEach(function (e) { line(' ' + fmtBytes(e.bytes) + ' ' + e.key); });
|
|
1674
|
+
if (inf.guides && inf.guides.count) { line('Saved TV guides (in StremioK’s folder): ' + fmtBytes(inf.guides.bytes) + ' in ' + inf.guides.count + (inf.guides.count === 1 ? ' file' : ' files')); }
|
|
1633
1675
|
if (inf.disk) { line('The TV’s own disk: ' + fmtBytes(inf.disk.free) + ' free of ' + fmtBytes(inf.disk.total) + ' (what the TV allows StremioK follows it: freeing space, unused apps for one, raises it)'); }
|
|
1634
1676
|
}
|
|
1635
1677
|
r.__onselect = function () {
|
|
1636
1678
|
if (!U.bigStore || !U.bigStore.info || _storageInfo === 'busy') { return; }
|
|
1637
1679
|
_storageInfo = 'busy'; renderSettings();
|
|
1638
|
-
|
|
1680
|
+
var guides = (typeof YTPlay !== 'undefined' && YTPlay.epgFiles) ? YTPlay.epgFiles().then(null, function () { return null; }) : null;
|
|
1681
|
+
Promise.all([U.bigStore.info(), U.diskInfo ? U.diskInfo() : null, guides]).then(function (both) {
|
|
1639
1682
|
var inf = both[0], ls = 0, lsAll = [];
|
|
1640
1683
|
inf.disk = both[1];
|
|
1684
|
+
inf.guides = both[2]; // ({ count, bytes }: the TV guides StremioK's service saved)
|
|
1641
1685
|
inf.lsKeys = {};
|
|
1642
1686
|
try {
|
|
1643
1687
|
for (var i = 0; i < window.localStorage.length; i++) {
|
|
@@ -1669,12 +1713,15 @@
|
|
|
1669
1713
|
}
|
|
1670
1714
|
var clr = U.el('div', 'addon-item focusable');
|
|
1671
1715
|
clr.appendChild(U.el('div', 'a-name', 'Clear saved copies'));
|
|
1672
|
-
clr.appendChild(U.el('div', 'a-url', 'Home’s saved rows, IPTV playlist copies, the USB index: made again when needed (playlists download again)'));
|
|
1716
|
+
clr.appendChild(U.el('div', 'a-url', 'Home’s saved rows, IPTV playlist copies, saved TV guides, the USB index: made again when needed (playlists and guides download again)'));
|
|
1673
1717
|
clr.__onselect = function () {
|
|
1674
|
-
confirm('Clear the copies StremioK can make again (Home rows, playlist copies, USB index)?', 'Clear', function () {
|
|
1718
|
+
confirm('Clear the copies StremioK can make again (Home rows, playlist copies, TV guides, USB index)?', 'Clear', function () {
|
|
1675
1719
|
U.bigStore.keys().then(function (ks) {
|
|
1676
1720
|
return Promise.all(ks.filter(function (k) { return /^stremiok\.(homecache|local\.index|iptv\.cache\.)/.test(k); }).map(function (k) { return U.bigStore.remove(k); }));
|
|
1677
1721
|
}).then(function () {
|
|
1722
|
+
return (typeof YTPlay !== 'undefined' && YTPlay.epgClear) ? YTPlay.epgClear().then(null, function () {}) : null; // (the saved guides: read again as they're needed)
|
|
1723
|
+
}).then(function () {
|
|
1724
|
+
if (typeof EPG !== 'undefined' && EPG.reset) { EPG.reset(); }
|
|
1678
1725
|
if (typeof LocalMedia !== 'undefined') { LocalMedia.clearIndex(); }
|
|
1679
1726
|
_storageInfo = null;
|
|
1680
1727
|
U.toast('Saved copies cleared');
|
|
@@ -1846,7 +1893,28 @@
|
|
|
1846
1893
|
// ----- Home feeds and Search results (Settings > Appearance): order, show / hide -----
|
|
1847
1894
|
// kind 'home': the Home rows (every catalog); 'search': the Search rows (every catalog
|
|
1848
1895
|
// that can search, IPTV as one per type). ▲/▼ moves one; OK on its name shows / hides it.
|
|
1896
|
+
// 'menu': the top menu between Watching and Settings (the tabs, Discover, Search: ViewParts'
|
|
1897
|
+
// menuOrder); OK on a tab shows / hides it, Discover and Search always show.
|
|
1898
|
+
var MENU_FIXED = { discover: 'Discover', search: 'Search' };
|
|
1899
|
+
function menuHidden(k) { return !MENU_FIXED[k] && (Stremio.getPrefs().hiddenTabs || []).indexOf(k) !== -1; }
|
|
1849
1900
|
var FEED_KINDS = {
|
|
1901
|
+
menu: { section: 'menuorder', title: 'Appearance › Menu', hint: 'Watching comes first and Settings last: ⇈ ▲ ▼ ⇊ put the rest in your order · OK on a tab shows/hides it.',
|
|
1902
|
+
list: function () {
|
|
1903
|
+
return Promise.resolve(V.menuOrder().map(function (k) {
|
|
1904
|
+
var t = (V.HOME_TABS || []).filter(function (x) { return x.key === k; })[0];
|
|
1905
|
+
return { key: k, name: MENU_FIXED[k] || (t ? t.label : k) + (k === 'local' ? ' (with a USB drive in)' : k === 'guide' ? ' (once a TV guide is read)' : '') };
|
|
1906
|
+
}));
|
|
1907
|
+
},
|
|
1908
|
+
keyOf: function (x) { return x.key; },
|
|
1909
|
+
move: function (k, d) { V.moveMenuItem(k, d); markHomeDirty(); return Promise.resolve(); },
|
|
1910
|
+
toggle: function (k) {
|
|
1911
|
+
if (MENU_FIXED[k]) { U.toast(MENU_FIXED[k] + ' is always in the menu'); return; }
|
|
1912
|
+
var now = (Stremio.getPrefs().hiddenTabs || []).filter(function (x) { return x !== k; });
|
|
1913
|
+
if (!menuHidden(k)) { now.push(k); }
|
|
1914
|
+
Stremio.setPref('hiddenTabs', now);
|
|
1915
|
+
markHomeDirty();
|
|
1916
|
+
},
|
|
1917
|
+
hidden: menuHidden },
|
|
1850
1918
|
home: { section: 'feeds', title: 'Appearance › Home feeds', hint: '▲/▼ reorder a feed · OK on the name shows/hides it on Home.',
|
|
1851
1919
|
list: function () { return Stremio.listCatalogsOrdered(); }, keyOf: function (c) { return Stremio.catalogKey(c); },
|
|
1852
1920
|
move: function (k, d) { return Stremio.moveFeed(k, d); }, toggle: function (k) { Stremio.toggleFeed(k); },
|
package/js/views.js
CHANGED
|
@@ -12,8 +12,9 @@ var Views = (function (V) {
|
|
|
12
12
|
// pages, a search's rows) stopped
|
|
13
13
|
leaveScreen: function (name) { var f = { detail: V.leaveDetail, discover: V.leaveDiscover, search: V.leaveSearch }[name]; if (f) { f(); } }, backInLocal: V.backInLocal, setHomeTab: V.setHomeTab,
|
|
14
14
|
renderLive: V.renderLive,
|
|
15
|
-
renderSearch: V.renderSearch, renderDiscover: V.renderDiscover, renderEntry: V.renderEntry, backInDetail: V.backInDetail,
|
|
15
|
+
renderSearch: V.renderSearch, searchFor: function (q) { V.searchFor(q); }, renderDiscover: V.renderDiscover, renderEntry: V.renderEntry, backInDetail: V.backInDetail,
|
|
16
16
|
homeDirty: V.homeDirty, refreshHomeIfStale: V.refreshHomeIfStale, alertNewEpisodes: V.alertNewEpisodes, previewTiles: V.previewTiles, withShowInfo: V.withShowInfo,
|
|
17
|
-
isModalOpen: V.isModalOpen, closeModal: V.closeModal, moveKey: V.moveKey, availableTabs: V.availableTabs
|
|
17
|
+
isModalOpen: V.isModalOpen, closeModal: V.closeModal, moveKey: V.moveKey, availableTabs: V.availableTabs,
|
|
18
|
+
menuItems: V.menuItems, menuOrder: V.menuOrder, moveMenuItem: V.moveMenuItem
|
|
18
19
|
};
|
|
19
20
|
})(ViewParts);
|