From 7360d7fe314ed487ebddf84793ef8d0c7fbc64e6 Mon Sep 17 00:00:00 2001 From: Vali Date: Tue, 18 Aug 2020 15:38:47 +0200 Subject: [PATCH] v1.1.0 --- CHANGELOG.md | 14 ++++ README.md | 2 +- css/find-the-culprit.css | 51 ++++++++++++ js/find-the-culprit.js | 164 +++++++++++++++++++++------------------ module.json | 1 + 5 files changed, 156 insertions(+), 76 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 css/find-the-culprit.css diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b40f180 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# v1.1.0 + +- You may now select a list of modules to keep active. +- Added statistics to each step: + - remaining modules in list + - remaining steps + - list of (in-)active modules +- Fixed some bug where (at least sometimes) the wrong result was shown +- Moved the "Find the culprit" button in module management to the bottom, right beside the "Save Module Settings" button. +- Cleaned up the code a bit. + +# v1.0.0 + +- Initial release \ No newline at end of file diff --git a/README.md b/README.md index a6af9c3..ede50f6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Find the culprit -GitHub release (latest by date) GitHub Releases [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?style=for-the-badge)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FYZ294SP2JBGS&source=url) +GitHub release (latest by date) GitHub Releases [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?style=for-the-badge)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FYZ294SP2JBGS&source=url) This module helps you debug compatibility issues of modules, by finding the module that is responsible for the issue, without having to manually activate and deactivate all your modules yourself. Just click the **Find the culprit** button in **Module Management** to start the process. * You will be asked to select a module to keep active at all times. Choose the module that you want to debug. diff --git a/css/find-the-culprit.css b/css/find-the-culprit.css new file mode 100644 index 0000000..c9904f3 --- /dev/null +++ b/css/find-the-culprit.css @@ -0,0 +1,51 @@ +.ftc-module-list { + + + background: rgba(0,0,0,0.05); + border: 2px inset #6666; + border-radius: 0.5em; + + padding: 0.5em; + list-style-type: disc; + max-height: 300px; + overflow-y: scroll; + padding: 0.5em; + list-style-type: none; +} + +.ftc-module-list li { + display: flex; + align-items: center; +} + +.ftc-module-list li input.ftc-checkbox { + margin-right: 0.5em; +} + +.ftc-module-list li:not(:first-child) { + border-top: 1px dashed #6666; +} + +.ftc-module-list i { + min-width: 1.5ch; +} + +.ftc-active { + color: green; +} + +.ftc-inactive { + color: red; +} + +.ftc-submit-div { + display: flex; +} + +.ftc-submit-div button { + flex: 1; +} + +.ftc-submit-div button[type="submit"] { + flex: 2; +} \ No newline at end of file diff --git a/js/find-the-culprit.js b/js/find-the-culprit.js index 0418b66..3efe51f 100644 --- a/js/find-the-culprit.js +++ b/js/find-the-culprit.js @@ -14,9 +14,15 @@ Hooks.on('renderModuleManagement', onRenderModuleManagement) function onRenderModuleManagement(app, html, options) { const form = html[0].querySelector('form'); - const btn = form.insertBefore(document.createElement('button'), form.children[0]); - btn.innerText = "Find the culprit!" + const submitBtn = form.querySelector('button[type="submit"]'); + const btn = document.createElement('button'); + btn.innerHTML = ' Find the culprit!'; btn.addEventListener('click', startDebugging); + const div = document.createElement('div'); + div.classList.add('ftc-submit-div'); + form.appendChild(div); + div.appendChild(btn); + div.appendChild(submitBtn); } function startDebugging(ev) { @@ -25,17 +31,16 @@ function startDebugging(ev) { let original = game.settings.get("core", ModuleManagement.CONFIG_SETTING); let settings = { original, - current: Object.keys(original).filter(e => original[e] && e !== moduleName), + active: Object.keys(original).filter(e => original[e] && e !== moduleName), step: 0 }; new Dialog({ title: 'Find the culprit', - content: `

Choose a module to keep active:

- + content: `

Choose modules to keep active:

+

After clicking start the page will refresh and you will be prompted to check whether your issue still exists. This will repeat multiple times until the culprit was found.

After the culprit was found you will be able to choose whether you want to reactivate all currently activated modules or not.

Don't worry if you accidently close one of the popups, just refresh the page manually and it will reappear.

`, @@ -44,11 +49,12 @@ function startDebugging(ev) { icon: '', label: "Start", callback: async (html) => { - const chosen = html[0].querySelector('select').value; - settings.current = settings.current.filter(e => e !== chosen); + const chosen = Array.from(html[0].querySelectorAll('input[type="checkbox"]:checked') || []).map(e => e.dataset.module); + + settings.active = settings.active.filter(e => !chosen.includes(e)); settings.chosen = chosen; await game.settings.set(moduleName, 'modules', settings); - deactivationStep(); + deactivationStep([]); } }, no: { @@ -68,51 +74,31 @@ async function doStep() { if (curr.step === 0) return doFirstStep(); - if (curr.current?.length > 1) return doBinarySearchStep(); + return doBinarySearchStep(); + // if (curr.active?.length) ; - new Dialog({ - title: 'Find the culprit, 1 Module active.', - content: `

Does your issue persist?

`, - buttons: { - yes: { - icon: '', - label: "Yes", - callback: async () => { - if (curr.last.length === 1) - return renderFinalDialog(curr.last[0]); - - curr.current = curr.last.slice(0, Math.floor(curr.last.length / 2)); - curr.last = curr.last.slice(Math.floor(curr.last.length / 2)); - await game.settings.set(moduleName, 'modules', curr); - deactivationStep(); - } - }, - no: { - icon: '', - label: "No", - callback: async () => { - renderFinalDialog(curr.current[0]); - } - } - } - }).render(true); } function renderFinalDialog(culprit) { - console.log(culprit); new Dialog({ title: 'Found the culprit!', - content: `

We found the culprit!

-

It is ${game.modules.get(culprit).data.title}

`, + content: `

We found the culprit!

+ `, buttons: { yes: { label: 'Reactivate all modules?', - callback: reactivateModules + callback: async () => { + await reactivateModules() + resetSettings(); + } }, no: { icon: '', label: "No", + callback: resetSettings } } }).render(true); @@ -121,31 +107,28 @@ function renderFinalDialog(culprit) { function doFirstStep() { const curr = game.settings.get(moduleName, 'modules'); new Dialog({ - title: 'Find the culprit, All modules deactivated.', - content: `

Does your issue persist?

`, + title: 'Find the culprit!', + content: `

All modules, except your chosen ones, are deactivated.

+

Does your issue persist?

`, buttons: { yes: { icon: '', label: "Yes", - callback: async () => { - const curr = game.settings.get(moduleName, 'modules'); - curr.step = 1; - await game.settings.set(moduleName, 'modules', curr); - deactivationStep(); - } - }, - no: { - icon: '', - label: "No", callback: () => { - const chosen = game.modules.get(curr.chosen)?.data?.title + const chosen = curr.chosen; new Dialog({ title: 'Find the Culprit', - content: `

Seems like the issue is a bug in ${chosen ? chosen : 'the core software'} itself!

`, + content: `

Seems like the issue is a bug in ${chosen?.length ? `your chosen module list: +

` : 'the core software.'}

`, buttons: { yes: { label: 'Reactivate all modules', - callback: reactivateModules + callback: async () => { + await reactivateModules() + resetSettings(); + } }, no: { icon: '', @@ -154,6 +137,16 @@ function doFirstStep() { } }).render(true); } + }, + no: { + icon: '', + label: "No", + callback: async () => { + const curr = game.settings.get(moduleName, 'modules'); + curr.step = 1; + await game.settings.set(moduleName, 'modules', curr); + deactivationStep(curr.active); + } } } }).render(true); @@ -161,45 +154,63 @@ function doFirstStep() { function doBinarySearchStep() { const curr = game.settings.get(moduleName, 'modules'); + const numActive = (curr.active?.length || 0), + numInactive = (curr.inactive?.length || 0), + stepsLeft = Math.ceil(Math.log2((numActive > numInactive ? numActive : numInactive))) + 1; new Dialog({ - title: `Find the culprit, ${curr.current.length} modules left`, - content: `

Does your issue persist?

`, + title: `Find the culprit`, + content: `

Current statistics

+

${numActive + numInactive} modules still in list.
+ Remaining steps ≤ ${stepsLeft}.
+ Current module list: +

+

+

+

Does your issue persist?

`, buttons: { yes: { icon: '', label: "Yes", callback: async () => { - curr.last = curr.current.slice(Math.floor(curr.current.length / 2)); - curr.current = curr.current.slice(0, Math.floor(curr.current.length / 2)); - await game.settings.set(moduleName, 'modules', curr); - deactivationStep(); + deactivationStep(curr.active); } }, no: { icon: '', label: "No", callback: async () => { - curr.last = curr.current.slice(0, Math.floor(curr.current.length / 2)); - curr.current = curr.current.slice(Math.floor(curr.current.length / 2) + 1); - await game.settings.set(moduleName, 'modules', curr); - deactivationStep(); + deactivationStep(curr.inactive); } } } }).render(true); } -function deactivationStep() { - const curr = game.settings.get(moduleName, 'modules'); - console.log(curr); +async function deactivationStep(chosenModules = []) { + if (chosenModules.length === 1) return renderFinalDialog(chosenModules[0]); + + const currSettings = game.settings.get(moduleName, 'modules'); + let original = game.settings.get("core", ModuleManagement.CONFIG_SETTING); + // deactivate all modules - const deactivate = Object.keys(original).filter(e => e !== curr.chosen && e !== moduleName); + const deactivate = Object.keys(original).filter(e => !currSettings.chosen.includes(e) && e !== moduleName); for (let module of deactivate) original[module] = false; - // activate only first half - for (let i = 0; i < Math.floor(curr.current.length / 2); i++) - original[curr.current[i]] = true; + + if (chosenModules.length > 0) { + const half = Math.ceil(chosenModules.length / 2); + currSettings.inactive = chosenModules.slice(half); + currSettings.active = chosenModules.slice(0, half); + // activate only first half + for (let module of currSettings.active) + original[module] = true; + + await game.settings.set(moduleName, 'modules', currSettings); + } game.settings.set('core', ModuleManagement.CONFIG_SETTING, original); } @@ -210,6 +221,9 @@ async function reactivateModules() { for (let mod in curr.original) original[mod] = curr.original[mod]; - await game.settings.set(moduleName, 'modules', {'-=step': null, '-=current': null, '-=original': null}); game.settings.set('core', ModuleManagement.CONFIG_SETTING, original); +} + +async function resetSettings() { + return game.settings.set(moduleName, 'modules', {'-=step': null, '-=active': null, '-=original': null, '-=inactive': null}); } \ No newline at end of file diff --git a/module.json b/module.json index 0d8f445..4cdb475 100644 --- a/module.json +++ b/module.json @@ -7,6 +7,7 @@ "compatibleCoreVersion": "1.0.0", "author": "Moerill", "esmodules": ["js/find-the-culprit.js"], + "styles": ["css/find-the-culprit.css"], "manifest": "https://raw.githubusercontent.com/Moerill/fvtt-find-the-culprit/master/module.json", "url": "https://github.com/Moerill/fvtt-find-the-culprit", "download": "https://github.com/Moerill/fvtt-find-the-culprit/releases/download/v1.0.0/v1.0.0.zip"