Restructured JS files
This commit is contained in:
@@ -60,17 +60,17 @@ async function build() {
|
|||||||
logLevel: 'warning',
|
logLevel: 'warning',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy worker files from src/js/ (not bundled, served separately)
|
// Copy worker files from src/js/workers/ (not bundled, served separately)
|
||||||
mkdirSync(join(distDir, 'js'), { recursive: true });
|
mkdirSync(join(distDir, 'js', 'workers'), { recursive: true });
|
||||||
|
|
||||||
// Copy wasm_exec.js as-is
|
// Copy wasm_exec.js as-is
|
||||||
const wasmExecSrc = join(srcDir, 'js', 'wasm_exec.js');
|
const wasmExecSrc = join(srcDir, 'js', 'wasm_exec.js');
|
||||||
if (existsSync(wasmExecSrc)) {
|
if (existsSync(wasmExecSrc)) {
|
||||||
cpSync(wasmExecSrc, join(distDir, 'js', 'wasm_exec.js'));
|
cpSync(wasmExecSrc, join(distDir, 'js', 'workers', 'wasm_exec.js'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy patch-worker.js with WASM hash injected
|
// Copy patch-worker.js with WASM hash injected
|
||||||
const workerSrc = join(srcDir, 'js', 'patch-worker.js');
|
const workerSrc = join(srcDir, 'js', 'workers', 'patch-worker.js');
|
||||||
if (existsSync(workerSrc)) {
|
if (existsSync(workerSrc)) {
|
||||||
let workerContent = readFileSync(workerSrc, 'utf-8');
|
let workerContent = readFileSync(workerSrc, 'utf-8');
|
||||||
const wasmFile = join(distDir, 'wasm', 'kobopatch.wasm');
|
const wasmFile = join(distDir, 'wasm', 'kobopatch.wasm');
|
||||||
@@ -81,7 +81,7 @@ async function build() {
|
|||||||
`kobopatch.wasm?h=${wasmHash}'`
|
`kobopatch.wasm?h=${wasmHash}'`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
writeFileSync(join(distDir, 'js', 'patch-worker.js'), workerContent);
|
writeFileSync(join(distDir, 'js', 'workers', 'patch-worker.js'), workerContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get git version string
|
// Get git version string
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Worker script uses importScripts, self, Go, globalThis, WebAssembly
|
// Worker script uses importScripts, self, Go, globalThis, WebAssembly
|
||||||
files: ['src/js/patch-worker.js'],
|
files: ['src/js/workers/patch-worker.js'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
self: 'readonly',
|
self: 'readonly',
|
||||||
|
|||||||
@@ -18,17 +18,17 @@
|
|||||||
* `state.showError()` when they need to cross module boundaries.
|
* `state.showError()` when they need to cross module boundaries.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KoboDevice } from './kobo-device.js';
|
import { KoboDevice } from './services/kobo-device.js';
|
||||||
import { loadSoftwareUrls, getSoftwareUrl, getDevicesForVersion } from './kobo-software-urls.js';
|
import { loadSoftwareUrls, getSoftwareUrl, getDevicesForVersion } from './services/kobo-software-urls.js';
|
||||||
import { PatchUI, scanAvailablePatches } from './patch-ui.js';
|
import { PatchUI, scanAvailablePatches } from './ui/patch-ui.js';
|
||||||
import { KoboPatchRunner } from './patch-runner.js';
|
import { KoboPatchRunner } from './services/patch-runner.js';
|
||||||
import { NickelMenuInstaller, ALL_FEATURES } from '../nickelmenu/installer.js';
|
import { NickelMenuInstaller, ALL_FEATURES } from '../nickelmenu/installer.js';
|
||||||
import { TL } from './strings.js';
|
import { TL } from './strings.js';
|
||||||
import { isEnabled as analyticsEnabled, track } from './analytics.js';
|
import { isEnabled as analyticsEnabled, track } from './analytics.js';
|
||||||
import { $, $q, populateSelect } from './dom.js';
|
import { $, $q, populateSelect } from './dom.js';
|
||||||
import { showStep, setNavLabels, setNavStep, hideNav, showNav, stepHistory, setupCardRadios } from './nav.js';
|
import { showStep, setNavLabels, setNavStep, hideNav, showNav, stepHistory, setupCardRadios } from './nav.js';
|
||||||
import { initNickelMenu } from './nickelmenu-flow.js';
|
import { initNickelMenu } from './flows/nickelmenu-flow.js';
|
||||||
import { initPatchesFlow } from './patches-flow.js';
|
import { initPatchesFlow } from './flows/patches-flow.js';
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Shared state
|
// Shared state
|
||||||
|
|||||||
@@ -44,6 +44,59 @@ export function populateSelect(selectEl, placeholder, items) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a list of checkbox items into a container.
|
||||||
|
* @param {HTMLElement} container
|
||||||
|
* @param {Array<{name: string, title: string, description: string, checked: boolean, disabled?: boolean}>} items
|
||||||
|
*/
|
||||||
|
export function renderNmCheckboxList(container, items) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
for (const item of items) {
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.className = 'nm-config-item';
|
||||||
|
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'checkbox';
|
||||||
|
input.name = item.name;
|
||||||
|
input.checked = item.checked;
|
||||||
|
if (item.disabled) input.disabled = true;
|
||||||
|
|
||||||
|
const textDiv = document.createElement('div');
|
||||||
|
textDiv.className = 'nm-config-text';
|
||||||
|
|
||||||
|
const titleSpan = document.createElement('span');
|
||||||
|
titleSpan.className = 'nm-config-title';
|
||||||
|
titleSpan.textContent = item.title;
|
||||||
|
|
||||||
|
const descSpan = document.createElement('span');
|
||||||
|
descSpan.className = 'nm-config-desc';
|
||||||
|
descSpan.textContent = item.description;
|
||||||
|
|
||||||
|
textDiv.appendChild(titleSpan);
|
||||||
|
textDiv.appendChild(descSpan);
|
||||||
|
label.appendChild(input);
|
||||||
|
label.appendChild(textDiv);
|
||||||
|
container.appendChild(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Populate a <ul>/<ol> with text items, clearing existing content. */
|
||||||
|
export function populateList(listEl, items) {
|
||||||
|
listEl.innerHTML = '';
|
||||||
|
for (const text of items) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.textContent = text;
|
||||||
|
listEl.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch with automatic error throwing on non-OK responses. */
|
||||||
|
export async function fetchOrThrow(url, errorPrefix = 'Fetch failed') {
|
||||||
|
const resp = await fetch(url);
|
||||||
|
if (!resp.ok) throw new Error(`${errorPrefix}: HTTP ${resp.status}`);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger a browser download of in-memory data.
|
* Trigger a browser download of in-memory data.
|
||||||
* Creates a temporary object URL, clicks a hidden <a>, then revokes it.
|
* Creates a temporary object URL, clicks a hidden <a>, then revokes it.
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
* `resetNickelMenuState`.
|
* `resetNickelMenuState`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $, $q, $qa, triggerDownload } from './dom.js';
|
import { $, $q, $qa, triggerDownload, renderNmCheckboxList, populateList } from '../dom.js';
|
||||||
import { showStep, setNavStep } from './nav.js';
|
import { showStep, setNavStep } from '../nav.js';
|
||||||
import { ALL_FEATURES } from '../nickelmenu/installer.js';
|
import { ALL_FEATURES } from '../../nickelmenu/installer.js';
|
||||||
import { TL } from './strings.js';
|
import { TL } from '../strings.js';
|
||||||
import { track } from './analytics.js';
|
import { track } from '../analytics.js';
|
||||||
|
|
||||||
export function initNickelMenu(state) {
|
export function initNickelMenu(state) {
|
||||||
|
|
||||||
@@ -47,42 +47,16 @@ export function initNickelMenu(state) {
|
|||||||
// Required features are checked and disabled; others use their default.
|
// Required features are checked and disabled; others use their default.
|
||||||
|
|
||||||
function renderFeatureCheckboxes() {
|
function renderFeatureCheckboxes() {
|
||||||
nmConfigOptions.innerHTML = '';
|
const items = ALL_FEATURES
|
||||||
for (const feature of ALL_FEATURES) {
|
.filter(f => f.available !== false)
|
||||||
if (feature.available === false) continue;
|
.map(f => ({
|
||||||
|
name: 'nm-cfg-' + f.id,
|
||||||
const label = document.createElement('label');
|
title: f.title + (f.required ? ' (required)' : '') + (f.version ? ' ' + f.version : ''),
|
||||||
label.className = 'nm-config-item';
|
description: f.description,
|
||||||
|
checked: f.required || f.default,
|
||||||
const input = document.createElement('input');
|
disabled: f.required,
|
||||||
input.type = 'checkbox';
|
}));
|
||||||
input.name = 'nm-cfg-' + feature.id;
|
renderNmCheckboxList(nmConfigOptions, items);
|
||||||
input.checked = feature.default;
|
|
||||||
if (feature.required) {
|
|
||||||
input.checked = true;
|
|
||||||
input.disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const textDiv = document.createElement('div');
|
|
||||||
textDiv.className = 'nm-config-text';
|
|
||||||
|
|
||||||
const titleSpan = document.createElement('span');
|
|
||||||
titleSpan.className = 'nm-config-title';
|
|
||||||
let titleText = feature.title;
|
|
||||||
if (feature.required) titleText += ' (required)';
|
|
||||||
if (feature.version) titleText += ' ' + feature.version;
|
|
||||||
titleSpan.textContent = titleText;
|
|
||||||
|
|
||||||
const descSpan = document.createElement('span');
|
|
||||||
descSpan.className = 'nm-config-desc';
|
|
||||||
descSpan.textContent = feature.description;
|
|
||||||
|
|
||||||
textDiv.appendChild(titleSpan);
|
|
||||||
textDiv.appendChild(descSpan);
|
|
||||||
label.appendChild(input);
|
|
||||||
label.appendChild(textDiv);
|
|
||||||
nmConfigOptions.appendChild(label);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Uninstall checkboxes ---
|
// --- Uninstall checkboxes ---
|
||||||
@@ -90,35 +64,17 @@ export function initNickelMenu(state) {
|
|||||||
// (like KOReader) so the user can opt into cleaning those up too.
|
// (like KOReader) so the user can opt into cleaning those up too.
|
||||||
|
|
||||||
function renderUninstallCheckboxes() {
|
function renderUninstallCheckboxes() {
|
||||||
|
if (detectedUninstallFeatures.length === 0) {
|
||||||
nmUninstallOptions.innerHTML = '';
|
nmUninstallOptions.innerHTML = '';
|
||||||
if (detectedUninstallFeatures.length === 0) return;
|
return;
|
||||||
|
|
||||||
for (const feature of detectedUninstallFeatures) {
|
|
||||||
const label = document.createElement('label');
|
|
||||||
label.className = 'nm-config-item';
|
|
||||||
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'checkbox';
|
|
||||||
input.name = 'nm-uninstall-' + feature.id;
|
|
||||||
input.checked = true;
|
|
||||||
|
|
||||||
const textDiv = document.createElement('div');
|
|
||||||
textDiv.className = 'nm-config-text';
|
|
||||||
|
|
||||||
const titleSpan = document.createElement('span');
|
|
||||||
titleSpan.className = 'nm-config-title';
|
|
||||||
titleSpan.textContent = 'Also remove ' + feature.uninstall.title;
|
|
||||||
|
|
||||||
const descSpan = document.createElement('span');
|
|
||||||
descSpan.className = 'nm-config-desc';
|
|
||||||
descSpan.textContent = feature.uninstall.description;
|
|
||||||
|
|
||||||
textDiv.appendChild(titleSpan);
|
|
||||||
textDiv.appendChild(descSpan);
|
|
||||||
label.appendChild(input);
|
|
||||||
label.appendChild(textDiv);
|
|
||||||
nmUninstallOptions.appendChild(label);
|
|
||||||
}
|
}
|
||||||
|
const items = detectedUninstallFeatures.map(f => ({
|
||||||
|
name: 'nm-uninstall-' + f.id,
|
||||||
|
title: 'Also remove ' + f.uninstall.title,
|
||||||
|
description: f.uninstall.description,
|
||||||
|
checked: true,
|
||||||
|
}));
|
||||||
|
renderNmCheckboxList(nmUninstallOptions, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clear removal state when returning to mode selection. */
|
/** Clear removal state when returning to mode selection. */
|
||||||
@@ -263,39 +219,24 @@ export function initNickelMenu(state) {
|
|||||||
function goToNmReview() {
|
function goToNmReview() {
|
||||||
const summary = $('nm-review-summary');
|
const summary = $('nm-review-summary');
|
||||||
const list = $('nm-review-list');
|
const list = $('nm-review-list');
|
||||||
list.innerHTML = '';
|
|
||||||
|
|
||||||
if (state.nickelMenuOption === 'remove') {
|
if (state.nickelMenuOption === 'remove') {
|
||||||
summary.textContent = TL.STATUS.NM_WILL_BE_REMOVED;
|
summary.textContent = TL.STATUS.NM_WILL_BE_REMOVED;
|
||||||
const featuresToRemove = getSelectedUninstallFeatures();
|
const featuresToRemove = getSelectedUninstallFeatures();
|
||||||
for (const feature of featuresToRemove) {
|
populateList(list, featuresToRemove.map(f => f.uninstall.title + ' will also be removed'));
|
||||||
const li = document.createElement('li');
|
|
||||||
li.textContent = feature.uninstall.title + ' will also be removed';
|
|
||||||
list.appendChild(li);
|
|
||||||
}
|
|
||||||
btnNmWrite.hidden = state.manualMode;
|
btnNmWrite.hidden = state.manualMode;
|
||||||
btnNmWrite.textContent = TL.BUTTON.REMOVE_FROM_KOBO;
|
btnNmWrite.textContent = TL.BUTTON.REMOVE_FROM_KOBO;
|
||||||
btnNmDownload.hidden = true;
|
btnNmDownload.hidden = true;
|
||||||
} else if (state.nickelMenuOption === 'nickelmenu-only') {
|
|
||||||
summary.textContent = TL.STATUS.NM_WILL_BE_INSTALLED;
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.textContent = TL.STATUS.NM_NICKEL_ROOT_TGZ;
|
|
||||||
list.appendChild(li);
|
|
||||||
btnNmWrite.hidden = false;
|
|
||||||
btnNmWrite.textContent = TL.BUTTON.WRITE_TO_KOBO;
|
|
||||||
btnNmDownload.hidden = false;
|
|
||||||
} else {
|
} else {
|
||||||
// "preset" — list NickelMenu plus all selected features.
|
// "nickelmenu-only" or "preset" — both install NickelMenu.
|
||||||
summary.textContent = TL.STATUS.NM_WILL_BE_INSTALLED;
|
summary.textContent = TL.STATUS.NM_WILL_BE_INSTALLED;
|
||||||
const items = [TL.STATUS.NM_NICKEL_ROOT_TGZ];
|
const items = [TL.STATUS.NM_NICKEL_ROOT_TGZ];
|
||||||
|
if (state.nickelMenuOption === 'preset') {
|
||||||
for (const feature of getSelectedFeatures()) {
|
for (const feature of getSelectedFeatures()) {
|
||||||
items.push(feature.title);
|
items.push(feature.title);
|
||||||
}
|
}
|
||||||
for (const text of items) {
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.textContent = text;
|
|
||||||
list.appendChild(li);
|
|
||||||
}
|
}
|
||||||
|
populateList(list, items);
|
||||||
btnNmWrite.hidden = false;
|
btnNmWrite.hidden = false;
|
||||||
btnNmWrite.textContent = TL.BUTTON.WRITE_TO_KOBO;
|
btnNmWrite.textContent = TL.BUTTON.WRITE_TO_KOBO;
|
||||||
btnNmDownload.hidden = false;
|
btnNmDownload.hidden = false;
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
* `updatePatchCount`, and `configureFirmwareStep`.
|
* `updatePatchCount`, and `configureFirmwareStep`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $, formatMB, triggerDownload } from './dom.js';
|
import { $, formatMB, triggerDownload, populateList } from '../dom.js';
|
||||||
import { showStep, setNavLabels, setNavStep } from './nav.js';
|
import { showStep, setNavLabels, setNavStep } from '../nav.js';
|
||||||
import { KoboModels } from './kobo-device.js';
|
import { KoboModels } from '../services/kobo-device.js';
|
||||||
import { TL } from './strings.js';
|
import { TL } from '../strings.js';
|
||||||
import { track } from './analytics.js';
|
import { track } from '../analytics.js';
|
||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
export function initPatchesFlow(state) {
|
export function initPatchesFlow(state) {
|
||||||
@@ -99,13 +99,8 @@ export function initPatchesFlow(state) {
|
|||||||
|
|
||||||
function populateSelectedPatchesList() {
|
function populateSelectedPatchesList() {
|
||||||
const patchList = $('selected-patches-list');
|
const patchList = $('selected-patches-list');
|
||||||
patchList.innerHTML = '';
|
|
||||||
const enabled = state.patchUI.getEnabledPatches();
|
const enabled = state.patchUI.getEnabledPatches();
|
||||||
for (const name of enabled) {
|
populateList(patchList, enabled);
|
||||||
const li = document.createElement('li');
|
|
||||||
li.textContent = name;
|
|
||||||
patchList.appendChild(li);
|
|
||||||
}
|
|
||||||
const hasPatches = enabled.length > 0;
|
const hasPatches = enabled.length > 0;
|
||||||
patchList.hidden = !hasPatches;
|
patchList.hidden = !hasPatches;
|
||||||
$('selected-patches-heading').hidden = !hasPatches;
|
$('selected-patches-heading').hidden = !hasPatches;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { KoboModels } from './kobo-device.js';
|
import { KoboModels } from './kobo-device.js';
|
||||||
|
import { fetchOrThrow } from '../dom.js';
|
||||||
|
|
||||||
let _data = null;
|
let _data = null;
|
||||||
|
|
||||||
@@ -8,8 +9,7 @@ let _data = null;
|
|||||||
*/
|
*/
|
||||||
async function loadSoftwareUrls() {
|
async function loadSoftwareUrls() {
|
||||||
if (_data) return _data;
|
if (_data) return _data;
|
||||||
const resp = await fetch('/patches/downloads.json');
|
const resp = await fetchOrThrow('/patches/downloads.json', 'Failed to load download URLs');
|
||||||
if (!resp.ok) throw new Error('Failed to load download URLs');
|
|
||||||
_data = await resp.json();
|
_data = await resp.json();
|
||||||
window.FIRMWARE_DOWNLOADS = _data;
|
window.FIRMWARE_DOWNLOADS = _data;
|
||||||
return _data;
|
return _data;
|
||||||
@@ -17,7 +17,7 @@ class KoboPatchRunner {
|
|||||||
*/
|
*/
|
||||||
patchFirmware(configYAML, firmwareZip, patchFiles, onProgress) {
|
patchFirmware(configYAML, firmwareZip, patchFiles, onProgress) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const worker = new Worker('js/patch-worker.js');
|
const worker = new Worker('js/workers/patch-worker.js');
|
||||||
this._worker = worker;
|
this._worker = worker;
|
||||||
|
|
||||||
worker.onmessage = (e) => {
|
worker.onmessage = (e) => {
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
import { TL } from './strings.js';
|
import { TL } from '../strings.js';
|
||||||
|
import { fetchOrThrow } from '../dom.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Friendly display names for patch files.
|
* Friendly display names for patch files.
|
||||||
@@ -208,10 +209,7 @@ class PatchUI {
|
|||||||
* Load patches from a URL pointing to a zip file.
|
* Load patches from a URL pointing to a zip file.
|
||||||
*/
|
*/
|
||||||
async loadFromURL(url) {
|
async loadFromURL(url) {
|
||||||
const resp = await fetch(url);
|
const resp = await fetchOrThrow(url, 'Failed to fetch patch zip');
|
||||||
if (!resp.ok) {
|
|
||||||
throw new Error('Failed to fetch patch zip: ' + resp.statusText);
|
|
||||||
}
|
|
||||||
const data = await resp.arrayBuffer();
|
const data = await resp.arrayBuffer();
|
||||||
await this.loadFromZip(data);
|
await this.loadFromZip(data);
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ async function loadWasm() {
|
|||||||
|
|
||||||
const go = new Go();
|
const go = new Go();
|
||||||
const result = await WebAssembly.instantiateStreaming(
|
const result = await WebAssembly.instantiateStreaming(
|
||||||
fetch('../wasm/kobopatch.wasm'),
|
fetch('../../wasm/kobopatch.wasm'),
|
||||||
go.importObject
|
go.importObject
|
||||||
);
|
);
|
||||||
go.run(result.instance);
|
go.run(result.instance);
|
||||||
23
web/src/nickelmenu/features/helpers.js
Normal file
23
web/src/nickelmenu/features/helpers.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Shared helpers for NickelMenu features that modify .adds/nm/items.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Create a postProcess function that appends a line to .adds/nm/items. */
|
||||||
|
export function appendToNmConfig(line) {
|
||||||
|
return function postProcess(files) {
|
||||||
|
const items = files.find(f => f.path === '.adds/nm/items');
|
||||||
|
if (!items || typeof items.data !== 'string') return files;
|
||||||
|
items.data += '\n' + line + '\n';
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a postProcess function that prepends a line to .adds/nm/items. */
|
||||||
|
export function prependToNmConfig(line) {
|
||||||
|
return function postProcess(files) {
|
||||||
|
const items = files.find(f => f.path === '.adds/nm/items');
|
||||||
|
if (!items || typeof items.data !== 'string') return files;
|
||||||
|
items.data = line + '\n\n' + items.data;
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
|
import { appendToNmConfig } from '../helpers.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: 'hide-notices',
|
id: 'hide-notices',
|
||||||
title: 'Hide home screen notices',
|
title: 'Hide home screen notices',
|
||||||
description: 'Hides the third row on the home screen that shows notices below your books, such as reading time, release notes for updates, and Kobo Plus or Store promotions.',
|
description: 'Hides the third row on the home screen that shows notices below your books, such as reading time, release notes for updates, and Kobo Plus or Store promotions.',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
||||||
postProcess(files) {
|
postProcess: appendToNmConfig('experimental:hide_home_row3_enabled:1'),
|
||||||
const items = files.find(f => f.path === '.adds/nm/items');
|
|
||||||
if (!items || typeof items.data !== 'string') return files;
|
|
||||||
|
|
||||||
items.data += '\nexperimental:hide_home_row3_enabled:1\n';
|
|
||||||
|
|
||||||
return files;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
|
import { appendToNmConfig } from '../helpers.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: 'hide-recommendations',
|
id: 'hide-recommendations',
|
||||||
title: 'Hide home screen recommendations',
|
title: 'Hide home screen recommendations',
|
||||||
description: 'Hides the recommendations next to your current read on the home screen.',
|
description: 'Hides the recommendations next to your current read on the home screen.',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
||||||
postProcess(files) {
|
postProcess: appendToNmConfig('experimental:hide_home_row1col2_enabled:1'),
|
||||||
const items = files.find(f => f.path === '.adds/nm/items');
|
|
||||||
if (!items || typeof items.data !== 'string') return files;
|
|
||||||
|
|
||||||
items.data += '\nexperimental:hide_home_row1col2_enabled:1\n';
|
|
||||||
|
|
||||||
return files;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
|
import { prependToNmConfig } from '../helpers.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: 'koreader',
|
id: 'koreader',
|
||||||
@@ -43,12 +44,5 @@ export default {
|
|||||||
return files;
|
return files;
|
||||||
},
|
},
|
||||||
|
|
||||||
postProcess(files) {
|
postProcess: prependToNmConfig('menu_item:main:KOReader:cmd_spawn:quiet:exec /mnt/onboard/.adds/koreader/koreader.sh'),
|
||||||
const items = files.find(f => f.path === '.adds/nm/items');
|
|
||||||
if (!items || typeof items.data !== 'string') return files;
|
|
||||||
|
|
||||||
items.data = 'menu_item:main:KOReader:cmd_spawn:quiet:exec /mnt/onboard/.adds/koreader/koreader.sh\n\n' + items.data;
|
|
||||||
|
|
||||||
return files;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { prependToNmConfig } from '../helpers.js';
|
||||||
|
|
||||||
const TAB_CONFIG = [
|
const TAB_CONFIG = [
|
||||||
'experimental :menu_main_15505_0_enabled: 1',
|
'experimental :menu_main_15505_0_enabled: 1',
|
||||||
'experimental :menu_main_15505_1_label: Books',
|
'experimental :menu_main_15505_1_label: Books',
|
||||||
@@ -17,12 +19,5 @@ export default {
|
|||||||
description: 'Hides the "My Notebooks" and "Discover" tabs from the bottom navigation tab bar.',
|
description: 'Hides the "My Notebooks" and "Discover" tabs from the bottom navigation tab bar.',
|
||||||
default: false,
|
default: false,
|
||||||
|
|
||||||
postProcess(files) {
|
postProcess: prependToNmConfig(TAB_CONFIG),
|
||||||
const items = files.find(f => f.path === '.adds/nm/items');
|
|
||||||
if (!items || typeof items.data !== 'string') return files;
|
|
||||||
|
|
||||||
items.data = TAB_CONFIG + '\n\n' + items.data;
|
|
||||||
|
|
||||||
return files;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
|
import { fetchOrThrow } from '../js/dom.js';
|
||||||
|
|
||||||
import customMenu from './features/custom-menu/index.js';
|
import customMenu from './features/custom-menu/index.js';
|
||||||
import readerlyFonts from './features/readerly-fonts/index.js';
|
import readerlyFonts from './features/readerly-fonts/index.js';
|
||||||
@@ -32,8 +33,7 @@ function createContext(feature, progressFn) {
|
|||||||
return {
|
return {
|
||||||
async asset(relativePath) {
|
async asset(relativePath) {
|
||||||
const url = basePath + relativePath;
|
const url = basePath + relativePath;
|
||||||
const resp = await fetch(url);
|
const resp = await fetchOrThrow(url, `Failed to load asset ${url}`);
|
||||||
if (!resp.ok) throw new Error(`Failed to load asset ${url}: HTTP ${resp.status}`);
|
|
||||||
return new Uint8Array(await resp.arrayBuffer());
|
return new Uint8Array(await resp.arrayBuffer());
|
||||||
},
|
},
|
||||||
progress(msg) {
|
progress(msg) {
|
||||||
@@ -53,8 +53,7 @@ export class NickelMenuInstaller {
|
|||||||
async loadNickelMenu(progressFn) {
|
async loadNickelMenu(progressFn) {
|
||||||
if (this.nickelMenuZip) return;
|
if (this.nickelMenuZip) return;
|
||||||
progressFn('Downloading NickelMenu...');
|
progressFn('Downloading NickelMenu...');
|
||||||
const resp = await fetch('nickelmenu/NickelMenu.zip');
|
const resp = await fetchOrThrow('nickelmenu/NickelMenu.zip', 'Failed to download NickelMenu.zip');
|
||||||
if (!resp.ok) throw new Error('Failed to download NickelMenu.zip: HTTP ' + resp.status);
|
|
||||||
this.nickelMenuZip = await JSZip.loadAsync(await resp.arrayBuffer());
|
this.nickelMenuZip = await JSZip.loadAsync(await resp.arrayBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user