InstaSynchP Settings

Provides the ability to store settings for the plugins

当前为 2015-05-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name InstaSynchP Settings
  3. // @namespace InstaSynchP
  4. // @description Provides the ability to store settings for the plugins
  5. // @version 1.1.0
  6. // @author Zod-
  7. // @source https://github.com/Zod-/InstaSynchP-Settings
  8. // @license MIT
  9. // @require https://greasyfork.org/scripts/5647-instasynchp-library/code/code.js?version=49210
  10. // @include *://instasync.com/r/*
  11. // @include *://*.instasync.com/r/*
  12. // @grant none
  13. // @run-at document-start
  14. // ==/UserScript==
  15.  
  16. function SettingsField(opts) {
  17. 'use strict';
  18. this.type = opts.type;
  19. this.label = opts.label || '';
  20. this.id = opts.id.replace(/ /g, '-');
  21. this.default = opts.default;
  22. this.title = opts.title || '';
  23. this.size = opts.size || 2;
  24. this.options = opts.options;
  25. this.section = opts.section || ['General'];
  26. this.disabled = opts.disabled || false;
  27. this.tooltipPlacement = opts.tooltipPlacement || 'top';
  28. this.destination = opts.destination || 'chat';
  29. this.hidden = opts.hidden || false;
  30. this.$div = $('<div>');
  31. this.$input = undefined;
  32. this.$tooltip = undefined;
  33. this.oldVal = undefined;
  34. this.val = undefined;
  35. this.init();
  36. this.buildDiv();
  37. }
  38.  
  39. SettingsField.prototype.init = function () {
  40. 'use strict';
  41. var _this = this;
  42. if (!window.localStorage.hasOwnProperty(_this.id)) {
  43. _this.setStorage(_this.default);
  44. }
  45. _this.getFromStorage();
  46. _this.oldVal = _this.val;
  47. };
  48.  
  49. SettingsField.prototype.getFromStorage = function () {
  50. 'use strict';
  51. var _this = this;
  52. var val = window.localStorage.getItem(_this.id);
  53.  
  54. switch (_this.type) {
  55. case 'checkbox':
  56. val = (val === 'true');
  57. break;
  58. case 'int':
  59. val = Number(val);
  60. break;
  61. }
  62.  
  63. _this.val = val;
  64. };
  65.  
  66. SettingsField.prototype.updateDisplay = function (fromGUI) {
  67. 'use strict';
  68. var _this = this;
  69. if (fromGUI) {
  70. return;
  71. }
  72. switch (_this.type) {
  73. case 'checkbox':
  74. _this.$input.prop('checked', _this.get());
  75. break;
  76. case 'int':
  77. case 'text':
  78. case 'select':
  79. _this.$input.val(_this.get());
  80. break;
  81. }
  82. };
  83.  
  84. SettingsField.prototype.get = function () {
  85. 'use strict';
  86. return this.val;
  87. };
  88.  
  89. SettingsField.prototype.setStorage = function (val) {
  90. 'use strict';
  91. var _this = this;
  92.  
  93. switch (_this.type) {
  94. case 'checkbox':
  95. val = !!val;
  96. break;
  97. }
  98.  
  99. window.localStorage.setItem(_this.id, val);
  100. };
  101.  
  102. SettingsField.prototype.set = function (val, fromGUI) {
  103. 'use strict';
  104. var _this = this;
  105. _this.oldVal = _this.val;
  106. _this.setStorage(val);
  107. _this.getFromStorage();
  108. _this.updateDisplay(fromGUI);
  109. if (_this.oldVal !== _this.val) {
  110. _this.onChange();
  111. }
  112. };
  113.  
  114. SettingsField.prototype.onChange = function () {
  115. 'use strict';
  116. events.fire('SettingChange[{0}]'.format(this.id), [this.oldVal, this.val]);
  117. };
  118.  
  119. SettingsField.prototype.createTooltip = function () {
  120. 'use strict';
  121. var _this = this;
  122. return $('<label>', {
  123. class: 'active_toolip',
  124. 'data-original-title': _this.title,
  125. 'data-placement': _this.tooltipPlacement
  126. }).tooltip();
  127. };
  128.  
  129. SettingsField.prototype.createInput = function () {
  130. 'use strict';
  131. var _this = this;
  132. switch (_this.type) {
  133. case 'checkbox':
  134. return _this.createCheckboxInput();
  135. case 'text':
  136. case 'int':
  137. return _this.createStringInput();
  138. case 'select':
  139. return _this.createSelectInput();
  140. //TODO remove when all types are implemented
  141. default:
  142. logger().warn(_this.name, 'settingtype not implemented yet ' + _this.type);
  143. _this.hidden = true;
  144. }
  145. };
  146.  
  147. SettingsField.prototype.createSelectInput = function () {
  148. 'use strict';
  149. var _this = this;
  150. _this.$div.addClass('instasync_settings_select');
  151. var $input = $('<select>', {
  152. id: 'instasyncp-settings-text-' + _this.id
  153. }).on('change', function () {
  154. _this.set($(this).val(), true);
  155. });
  156. _this.options.forEach(function (option) {
  157. $input.append(
  158. $('<option>').text(option)
  159. );
  160. });
  161. $input.val(_this.get());
  162. return $input;
  163. };
  164.  
  165. SettingsField.prototype.createStringInput = function () {
  166. 'use strict';
  167. var _this = this;
  168. _this.$div.addClass('instasync_settings_text');
  169. return $('<input>', {
  170. id: 'instasyncp-settings-text-' + _this.id,
  171. type: 'text'
  172. }).val(_this.get()).attr('size', _this.size).bind('input', function () {
  173. _this.set($(this).val(), true);
  174. });
  175. };
  176.  
  177. SettingsField.prototype.createCheckboxInput = function () {
  178. 'use strict';
  179. var _this = this;
  180. _this.$div.addClass('checkbox');
  181. return $('<input>', {
  182. id: 'instasyncp-settings-checkbox-' + _this.id,
  183. type: 'checkbox'
  184. }).prop('checked', _this.get()).change(function () {
  185. _this.set($(this).is(':checked'), true);
  186. });
  187. };
  188.  
  189. SettingsField.prototype.setLabel = function (label) {
  190. 'use strict';
  191. var _this = this;
  192. _this.label = label;
  193. _this.$input.detach();
  194. _this.$tooltip.empty().append(_this.$input).append(_this.label);
  195. };
  196.  
  197. SettingsField.prototype.setTitle = function (title) {
  198. 'use strict';
  199. var _this = this;
  200. _this.title = title;
  201. _this.$tooltip.attr('data-original-title', _this.title);
  202. };
  203.  
  204. SettingsField.prototype.buildDiv = function () {
  205. 'use strict';
  206. var _this = this;
  207. _this.$tooltip = _this.createTooltip();
  208. _this.$input = _this.createInput();
  209. if (_this.$input) {
  210. _this.$input.attr('disabled', _this.disabled);
  211. }
  212. _this.$div.append(_this.$tooltip.append(_this.$input).append(_this.label));
  213. if (_this.hidden) {
  214. _this.$div.hide();
  215. }
  216. };
  217.  
  218. function Settings() {
  219. 'use strict';
  220. this.version = '1.1.0';
  221. this.name = 'InstaSynchP Settings';
  222. this.fields = [];
  223. this.SettingsField = SettingsField;
  224. this.styles = [{
  225. name: 'instasync-settings',
  226. url: 'https://cdn.rawgit.com/Zod-/InstaSynchP-Settings/6a74f6d8b9351082e7c32459f5014fbd63a477b0/dist/settings.css',
  227. autoload: true
  228. }];
  229. this.destinations = {
  230. chat: '#tabs_chat_settings_content',
  231. playlist: '#tabs_playlist_settings',
  232. plugin: '#tabs_plugin_list_content'
  233. };
  234. this.settings = [{
  235. label: 'Interval to check for updates',
  236. title: 'The script will check for updates in the selected' +
  237. ' interval or on every refresh',
  238. id: 'update-timer',
  239. type: 'select',
  240. options: ['10m', '20m', '30m', '1h', 'on refresh'],
  241. 'default': '30m',
  242. section: ['Plugins']
  243. }, {
  244. label: 'Autosync',
  245. id: 'instasync-autosync',
  246. type: 'checkbox',
  247. destination: 'playlist',
  248. 'default': true,
  249. section: ['InstaSync']
  250. }, {
  251. label: 'Native YouTube controls',
  252. id: 'instasync-yt-controls',
  253. type: 'checkbox',
  254. destination: 'playlist',
  255. 'default': false,
  256. section: ['InstaSync']
  257. }, {
  258. label: 'Show greynames in chat',
  259. id: 'instasync-greynames-chat',
  260. type: 'checkbox',
  261. 'default': true,
  262. section: ['InstaSync']
  263. }, {
  264. label: 'Disable player',
  265. id: 'instasync-disable-player',
  266. type: 'checkbox',
  267. destination: 'playlist',
  268. 'default': false,
  269. section: ['InstaSync']
  270. }];
  271. var temp = {
  272. InstaSync: {
  273. fields: []
  274. }
  275. };
  276. this.sections = {
  277. chat: JSON.parse(JSON.stringify(temp)),
  278. playlist: JSON.parse(JSON.stringify(temp)),
  279. plugin: JSON.parse(JSON.stringify(temp))
  280. };
  281. this.pluginNames = {
  282. 'Core': ['Core', 'Event Hooks', 'CSSLoader', 'Settings', 'Commands',
  283. 'Logger'
  284. ],
  285. 'Chat': ['ModSpy', 'UserSpy', 'Input History', 'Autocomplete',
  286. 'Emote Names', 'Name Completion', 'SysMessage Hide', 'Timestamp'
  287. ],
  288. 'General': ['Layouts', 'Poll Menu', 'Bibby', 'Persistent Settings'],
  289. 'Commands': ['Bump', 'TrimWall'],
  290. 'Playlist': ['Wallcounter', 'History']
  291. };
  292. this.plugins = [];
  293. this.updateIntervalId = undefined;
  294. }
  295.  
  296. Settings.prototype.removeInstaSyncSettings = function () {
  297. 'use strict';
  298. $('#toggle_greyname_chat').parent().parent().remove();
  299. $('#toggle_show_joined').parent().parent().remove();
  300. $('#toggleYTcontrols_box').parent().parent().remove();
  301. $('#toggle_autosync_box').parent().parent().remove();
  302. };
  303.  
  304. Settings.prototype.postConnect = function () {
  305. 'use strict';
  306. var _this = this;
  307. window.room.autosync = _this.get('instasync-autosync');
  308. window.room.showYTcontrols = _this.get('instasync-yt-controls');
  309. window.room.filterGreyname = _this.get('instasync-greynames-chat');
  310. window.room.playerDisabled = _this.get('instasync-disable-player');
  311. reloadPlayer();
  312. };
  313.  
  314. Settings.prototype.preConnect = function () {
  315. 'use strict';
  316. var _this = this;
  317. $('#disable_player').remove();
  318. $('#reload_btn').off().on('click', function () {
  319. if (!_this.get('instasync-disable-player')) {
  320. reloadPlayer();
  321. } else {
  322. _this.set('instasync-disable-player', false);
  323. }
  324. });
  325. };
  326.  
  327. Settings.prototype.createResetButtons = function () {
  328. 'use strict';
  329. var _this = this;
  330. var $resetButton = $('<button>', {
  331. id: 'instasyncp-settings-reset',
  332. class: 'btn btn-xs btn-danger btn-primary',
  333. title: 'Reset the settings in this tab'
  334. }).text('Reset InstaSyncP Settings');
  335. Object.keys(_this.destinations).forEach(function (destination) {
  336. $(_this.destinations[destination]).append(
  337. $resetButton.clone().click(function () {
  338. _this.reset(destination);
  339. }).tooltip()
  340. );
  341. });
  342. $(_this.destinations.plugin).append(
  343. $('<button>', {
  344. id: 'instasyncp-settings-refresh',
  345. class: 'btn btn-xs btn-danger btn-primary',
  346. title: 'Apply changes by reloading the page'
  347. }).text('Apply Changes (Refresh)').click(function () {
  348. location.reload();
  349. }).tooltip()
  350. );
  351. };
  352.  
  353. Settings.prototype.createFields = function () {
  354. 'use strict';
  355. var _this = this;
  356. var newFields = {};
  357. _this.fields.forEach(function (field) {
  358. field = new _this.SettingsField(field);
  359. newFields[field.id] = field;
  360. _this.addToSection(field);
  361. });
  362. _this.sortSections();
  363. _this.fields = newFields;
  364. };
  365.  
  366. Settings.prototype.forEachDestination = function (callback) {
  367. 'use strict';
  368. var _this = this;
  369. Object.keys(_this.sections).forEach(function (destinationName) {
  370. callback({
  371. name: destinationName,
  372. value: _this.sections[destinationName]
  373. });
  374. });
  375. };
  376.  
  377. Settings.prototype.forEachSection = function (callback) {
  378. 'use strict';
  379. var _this = this;
  380. _this.forEachDestination(function (destinationPair) {
  381. Object.keys(destinationPair.value).forEach(function (sectionName) {
  382. callback(destinationPair, {
  383. name: sectionName,
  384. value: destinationPair.value[sectionName]
  385. });
  386. });
  387. });
  388. };
  389.  
  390. Settings.prototype.forEachField = function (callback) {
  391. 'use strict';
  392. var _this = this;
  393. _this.forEachSection(function (destinationPair, sectionPair) {
  394. sectionPair.value.fields.forEach(function (field) {
  395. callback(destinationPair, sectionPair, field);
  396. });
  397. });
  398. };
  399.  
  400. Settings.prototype.sortSections = function () {
  401. 'use strict';
  402. var _this = this;
  403. _this.forEachSection(function (destination, section) {
  404. section.value.fields.sort(function (field, otherField) {
  405. return field.label.localeCompare(otherField.label);
  406. });
  407. });
  408. };
  409.  
  410. Settings.prototype.addToSection = function (field) {
  411. 'use strict';
  412. var _this = this;
  413. if (field.hidden) {
  414. return;
  415. }
  416. var sectionName = field.section[1] || field.section[0];
  417. var destination = _this.sections[field.destination];
  418. if (!destination.hasOwnProperty(sectionName)) {
  419. destination[sectionName] = {
  420. fields: []
  421. };
  422. }
  423. destination[sectionName].fields.push(field);
  424. };
  425.  
  426. Settings.prototype.addFieldsToSite = function () {
  427. 'use strict';
  428. var _this = this;
  429. _this.forEachField(function (destinationPair, sectionPair, field) {
  430. var destinationSelector = _this.destinations[destinationPair.name];
  431. if (!sectionPair.value.isCreated) {
  432. $(destinationSelector).append(
  433. $('<div>', {
  434. class: 'instasync_settings_section'
  435. }).text(sectionPair.name)
  436. );
  437. sectionPair.value.isCreated = true;
  438. }
  439.  
  440. $(destinationSelector).append(field.$div);
  441. });
  442. };
  443.  
  444. Settings.prototype.createPluginTab = function () {
  445. 'use strict';
  446. var $navTab = createNavTab({
  447. tooltip: 'Plugins',
  448. tooltipPlacement: 'top',
  449. tab: '#tabs_plugin_list_content',
  450. class: 'fa fa-plug'
  451. });
  452. $navTab.find('i').before(
  453. $('<span>', {
  454. class: 'badge unread-msg-count updates'
  455. })
  456. );
  457. $('.chat-tabs').append($navTab);
  458. $('.chat-tabs-content').append(
  459. $('<div>', {
  460. class: 'tab-pane',
  461. id: 'tabs_plugin_list_content'
  462. })
  463. );
  464. };
  465.  
  466. Settings.prototype.createPluginFields = function () {
  467. 'use strict';
  468. var _this = this;
  469. Object.keys(_this.pluginNames).forEach(function (sectionName) {
  470. var section = _this.pluginNames[sectionName];
  471. section.forEach(function (pluginName, index) {
  472. var id;
  473. var disabled = false;
  474. if (sectionName === 'Commands') {
  475. id = 'InstaSynchP {0} Command'.format(pluginName);
  476. } else {
  477. id = 'InstaSynchP {0}'.format(pluginName);
  478. }
  479. if (sectionName === 'Core') {
  480. disabled = true;
  481. }
  482. _this.pluginNames[sectionName][index] = id;
  483. _this.fields.push({
  484. id: id,
  485. label: pluginName,
  486. type: 'checkbox',
  487. destination: 'plugin',
  488. disabled: disabled,
  489. 'default': true,
  490. section: [sectionName]
  491. });
  492. });
  493. _this.plugins = _this.plugins.concat(_this.pluginNames[sectionName]);
  494. });
  495. _this.fields.push({
  496. id: 'plugins-count',
  497. type: 'int',
  498. hidden: true,
  499. 'default': _this.plugins.length
  500. });
  501. };
  502.  
  503. Settings.prototype.disablePlugins = function () {
  504. 'use strict';
  505. var _this = this;
  506. Object.keys(window.plugins).forEach(function (pluginName) {
  507. var plugin = window.plugins[pluginName];
  508. plugin.enabled = _this.get(plugin.name.replace(/ /g, '-'), true);
  509. });
  510. };
  511.  
  512. Settings.prototype.collectSettings = function () {
  513. 'use strict';
  514. var _this = this;
  515. Object.keys(window.plugins).forEach(function (pluginName) {
  516. var plugin = window.plugins[pluginName];
  517. if (Array.isArray(plugin.settings)) {
  518. _this.fields = _this.fields.concat(plugin.settings);
  519. }
  520. if (Array.isArray(plugin.styles)) {
  521. plugin.styles.forEach(function (style) {
  522. _this.fields.push({
  523. label: '',
  524. id: style.name + '-css-content',
  525. type: 'text',
  526. hidden: true,
  527. value: '',
  528. section: ['Core']
  529. });
  530. _this.fields.push({
  531. label: '',
  532. id: style.name + '-css-url',
  533. type: 'text',
  534. hidden: true,
  535. value: '',
  536. section: ['Core']
  537. });
  538. events.on(plugins.cssLoader, 'ExecuteOnce', function () {
  539. plugins.cssLoader.addStyle(style);
  540. });
  541. });
  542. }
  543. });
  544. };
  545.  
  546. Settings.prototype.persistentSettings = function () {
  547. 'use strict';
  548. var _this = this;
  549. events.on(_this, 'SettingChange[instasync-autosync]', function (ig, v) {
  550. window.room.autosync = v;
  551. if (v) {
  552. sendcmd('resynch');
  553. }
  554. });
  555. events.on(_this, 'SettingChange[instasync-yt-controls]', function (ig, v) {
  556. window.room.showYTcontrols = v;
  557. reloadPlayer();
  558. });
  559. events.on(_this, 'SettingChange[instasync-greynames-chat]', function (ig, v) {
  560. window.room.filterGreyname = v;
  561. });
  562. events.on(_this, 'SettingChange[instasync-disable-player]', function (ig, v) {
  563. window.room.playerDisabled = v;
  564. if (v) {
  565. $('#media').html('');
  566. } else {
  567. reloadPlayer();
  568. }
  569. });
  570. };
  571.  
  572. Settings.prototype.executeOnce = function () {
  573. 'use strict';
  574. var _this = this;
  575.  
  576. function startTimer(timeString) {
  577. if (_this.updateIntervalId) {
  578. clearInterval(_this.updateIntervalId);
  579. _this.updateIntervalId = undefined;
  580. }
  581. if (timeString === 'on refresh') {
  582. return;
  583. }
  584. _this.updateIntervalId = setInterval(function () {
  585. _this.searchPluginUpdates();
  586. }, getTime(timeString) * 1000);
  587. }
  588.  
  589. events.on(_this, 'SettingChange[update-timer]', function (ignore, newVal) {
  590. _this.searchPluginUpdates();
  591. startTimer(newVal);
  592. });
  593.  
  594. startTimer(_this.get('update-timer'));
  595. };
  596.  
  597. Settings.prototype.searchPluginUpdates = function () {
  598. 'use strict';
  599. var _this = this;
  600. var updatesCount = 0;
  601. if (_this.get('plugins-count') !== _this.plugins.length) {
  602. updatesCount += Math.abs(_this.get('plugins-count') - _this.plugins.length);
  603. _this.set('plugins-count', _this.plugins.length);
  604. }
  605.  
  606. function done() {
  607. if (updatesCount > 0) {
  608. $('.updates').text(updatesCount);
  609. }
  610. }
  611.  
  612. function updateLabel(data) {
  613. if (!_this.plugins.contains(data.name)) {
  614. return;
  615. }
  616. var url = data.url;
  617. var label = '';
  618. var name = '';
  619. var install = '';
  620. var version = '';
  621. var info = '';
  622. var feedback = '';
  623. var plugin = {};
  624. Object.keys(window.plugins).forEach(function (pluginName) {
  625. var p = window.plugins[pluginName];
  626. if (p.name === data.name) {
  627. plugin = p;
  628. }
  629. });
  630. name = data.name.replace(/^InstaSynchP/i, '')
  631. .replace(/Command$/i, '').trim();
  632. install = ('<a class="install_link links"' +
  633. ' href="{0}/{1}" target="_blank">{2}</a>')
  634. .format(url, 'code.user.js');
  635. info = ('<a class="info_link links" href="{0}"' +
  636. ' target="_blank">info</a>').format(url);
  637. feedback = ('<a class="feedback_link links" href="{0}/{1}"' +
  638. ' target="_blank">{1}</a>').format(url, 'feedback');
  639.  
  640. if (plugin.version) {
  641. version = '<span class="{1} version_link links">v{0}</span>'
  642. .format(plugin.version, '{0}');
  643. if (plugin.version === data.version) {
  644. install = '';
  645. version = version.format('current_version_link');
  646. } else {
  647. updatesCount += 1;
  648. install = install.format('', '', 'update');
  649. version = version.format('outdated_version_link');
  650. }
  651. } else {
  652. install = install.format('', '', 'install');
  653. }
  654. if (_this.pluginNames.Core.contains(data.name) && name !== 'Core') {
  655. install = '';
  656. }
  657.  
  658. label = '{0} {1} {2} {3} {4}'
  659. .format(name, version, install, info, feedback).replace(/\s+/, ' ');
  660.  
  661. _this.fields[data.name.replace(/ /g, '-')].setLabel(label);
  662. _this.fields[data.name.replace(/ /g, '-')].setTitle(data.description);
  663. }
  664.  
  665.  
  666. $.getJSON('https://greasyfork.org/en/scripts.json?set=1666', function (data) {
  667. data.forEach(function (plugin) {
  668. updateLabel(plugin);
  669. });
  670. done();
  671. });
  672. };
  673.  
  674. Settings.prototype.executeOnceCore = function () {
  675. 'use strict';
  676. var _this = this;
  677. _this.collectSettings();
  678. _this.removeInstaSyncSettings();
  679. _this.persistentSettings();
  680. _this.createPluginTab();
  681. _this.createPluginFields();
  682. _this.createResetButtons();
  683. _this.createFields();
  684. _this.addFieldsToSite();
  685. _this.disablePlugins();
  686. _this.searchPluginUpdates();
  687.  
  688. $('#tabs_playlist_settings').append(
  689. $('#tabs_playlist_settings .mod-control').detach()
  690. );
  691. };
  692.  
  693. Settings.prototype.reset = function (destination) {
  694. 'use strict';
  695. this.forEachField(function (destinationPair, sectionPair, field) {
  696. if (destinationPair.name === destination && !field.hidden) {
  697. field.set(field.default);
  698. }
  699. });
  700. };
  701.  
  702. Settings.prototype.log = function (opts) {
  703. 'use strict';
  704. var args = [];
  705. opts.type = opts.type || 'debug';
  706. args.push(this.name);
  707. args.push(opts.event);
  708. logger()[opts.type].apply(logger(), args);
  709. };
  710.  
  711. Settings.prototype.get = function (id, fallback) {
  712. 'use strict';
  713. var _this = this;
  714. if (!_this.fields.hasOwnProperty(id)) {
  715. _this.log({
  716. event: 'getting a setting that does not exist ' + id,
  717. type: 'warn'
  718. });
  719. return fallback;
  720. }
  721. return _this.fields[id].get();
  722. };
  723.  
  724. Settings.prototype.set = function (id, newVal) {
  725. 'use strict';
  726. var _this = this;
  727. if (_this.fields.hasOwnProperty(id)) {
  728. _this.fields[id].set(newVal);
  729. } else {
  730. _this.log({
  731. event: 'setting a setting that does not exist ' + id,
  732. type: 'warn'
  733. });
  734. }
  735. };
  736.  
  737. Settings.prototype.save = function () {
  738. 'use strict';
  739. };
  740.  
  741. window.plugins = window.plugins || {};
  742. window.plugins.settings = new Settings();
  743. //TODO remove after removing references in other plugins
  744. window.gmc = window.plugins.settings;