tiny-essentials 1.26.3 → 1.27.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/.github/workflows/node.js.yml +1 -1
- package/README.md +0 -1
- package/changelog/1/26/4.md +10 -0
- package/changelog/1/27/0.md +24 -0
- package/dist/v1/ColorSafeStringify.min.js +1 -1
- package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
- package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
- package/dist/v1/TinyAnalogClock.min.js +1 -1
- package/dist/v1/TinyArrayComparator.min.js +1 -1
- package/dist/v1/TinyArrayPaginator.min.js +1 -1
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyClassManager.min.js +1 -1
- package/dist/v1/TinyClipboard.min.js +1 -1
- package/dist/v1/TinyColorConverter.min.js +1 -1
- package/dist/v1/TinyColorValidator.min.js +1 -1
- package/dist/v1/TinyCookieConsent.min.js +1 -1
- package/dist/v1/TinyDayNightCycle.min.js +1 -1
- package/dist/v1/TinyDomReadyManager.min.js +1 -1
- package/dist/v1/TinyDragDropDetector.min.js +1 -1
- package/dist/v1/TinyDragger.min.js +1 -1
- package/dist/v1/TinyElementObserver.min.js +1 -1
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyEvents.min.js +1 -1
- package/dist/v1/TinyGamepad.min.js +1 -1
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinyI18.min.js +1 -1
- package/dist/v1/TinyIframeEvents.min.js +1 -1
- package/dist/v1/TinyInventory.min.js +1 -1
- package/dist/v1/TinyInventoryTrader.min.js +1 -1
- package/dist/v1/TinyLevelUp.min.js +1 -1
- package/dist/v1/TinyLoadingScreen.min.js +1 -1
- package/dist/v1/TinyLocalStorage.min.js +1 -1
- package/dist/v1/TinyMaInSys.min.js +1 -1
- package/dist/v1/TinyMediaPlayer.min.js +1 -0
- package/dist/v1/TinyNeedBar.min.js +1 -1
- package/dist/v1/TinyNewWinEvents.min.js +1 -1
- package/dist/v1/TinyNotifications.min.js +1 -1
- package/dist/v1/TinyNotifyCenter.min.js +1 -1
- package/dist/v1/TinyPromiseQueue.min.js +1 -1
- package/dist/v1/TinyRadioFm.min.js +1 -0
- package/dist/v1/TinyRateLimiter.min.js +1 -1
- package/dist/v1/TinySimpleDice.min.js +1 -1
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyTextDiffer.min.js +1 -1
- package/dist/v1/TinyTextRangeEditor.min.js +1 -1
- package/dist/v1/TinyTextarea.min.js +1 -1
- package/dist/v1/TinyTimeout.min.js +1 -1
- package/dist/v1/TinyToastNotify.min.js +1 -1
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/UltraRandomMsgGen.min.js +1 -1
- package/dist/v1/basics/index.cjs +5 -0
- package/dist/v1/basics/index.d.mts +5 -1
- package/dist/v1/basics/index.mjs +2 -1
- package/dist/v1/basics/mediaContent.cjs +644 -0
- package/dist/v1/basics/mediaContent.d.mts +296 -0
- package/dist/v1/basics/mediaContent.mjs +557 -0
- package/dist/v1/build/TinyMediaPlayer.cjs +7 -0
- package/dist/v1/build/TinyMediaPlayer.d.mts +3 -0
- package/dist/v1/build/TinyMediaPlayer.mjs +2 -0
- package/dist/v1/build/TinyRadioFm.cjs +7 -0
- package/dist/v1/build/TinyRadioFm.d.mts +3 -0
- package/dist/v1/build/TinyRadioFm.mjs +2 -0
- package/dist/v1/index.cjs +9 -0
- package/dist/v1/index.d.mts +7 -1
- package/dist/v1/index.mjs +5 -2
- package/dist/v1/libs/ColorSafeStringify.d.mts +1 -1
- package/dist/v1/libs/TinyAfterScrollWatcher.cjs +16 -3
- package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +7 -3
- package/dist/v1/libs/TinyAfterScrollWatcher.mjs +13 -3
- package/dist/v1/libs/TinyArrayComparator.cjs +14 -11
- package/dist/v1/libs/TinyArrayComparator.d.mts +22 -19
- package/dist/v1/libs/TinyArrayComparator.mjs +14 -11
- package/dist/v1/libs/TinyArrayPaginator.cjs +23 -16
- package/dist/v1/libs/TinyArrayPaginator.d.mts +57 -30
- package/dist/v1/libs/TinyArrayPaginator.mjs +22 -16
- package/dist/v1/libs/TinyClassManager/TinyPluginInliner.cjs +265 -0
- package/dist/v1/libs/TinyClassManager/TinyPluginInliner.d.mts +92 -0
- package/dist/v1/libs/TinyClassManager/TinyPluginInliner.mjs +223 -0
- package/dist/v1/libs/TinyClassManager.cjs +124 -23
- package/dist/v1/libs/TinyClassManager.d.mts +33 -13
- package/dist/v1/libs/TinyClassManager.mjs +108 -23
- package/dist/v1/libs/TinyColorValidator.d.mts +16 -16
- package/dist/v1/libs/TinyDragDropDetector.cjs +9 -34
- package/dist/v1/libs/TinyDragDropDetector.d.mts +7 -21
- package/dist/v1/libs/TinyDragDropDetector.mjs +9 -24
- package/dist/v1/libs/TinyDragger.cjs +5 -5
- package/dist/v1/libs/TinyDragger.d.mts +4 -3
- package/dist/v1/libs/TinyDragger.mjs +5 -5
- package/dist/v1/libs/TinyElementObserver.cjs +6 -21
- package/dist/v1/libs/TinyElementObserver.d.mts +6 -15
- package/dist/v1/libs/TinyElementObserver.mjs +6 -20
- package/dist/v1/libs/TinyEvents.cjs +7 -0
- package/dist/v1/libs/TinyEvents.d.mts +4 -0
- package/dist/v1/libs/TinyEvents.mjs +6 -0
- package/dist/v1/libs/TinyGamepad.cjs +4 -6
- package/dist/v1/libs/TinyGamepad.d.mts +1 -1
- package/dist/v1/libs/TinyHtml/TinyHtmlIcon.d.mts +1 -1
- package/dist/v1/libs/TinyHtml/TinyHtmlTemplate.d.mts +1 -1
- package/dist/v1/libs/TinyHtml/TinyHtmlTextarea.d.mts +1 -1
- package/dist/v1/libs/TinyHtml/index.d.mts +1 -1
- package/dist/v1/libs/TinyHtml.cjs +72 -98
- package/dist/v1/libs/TinyHtml.d.mts +15 -32
- package/dist/v1/libs/TinyHtml.mjs +53 -77
- package/dist/v1/libs/TinyInventory.d.mts +1 -1
- package/dist/v1/libs/TinyMediaPlayer.cjs +844 -0
- package/dist/v1/libs/TinyMediaPlayer.d.mts +306 -0
- package/dist/v1/libs/TinyMediaPlayer.mjs +755 -0
- package/dist/v1/libs/TinyRadioFm.cjs +1378 -0
- package/dist/v1/libs/TinyRadioFm.d.mts +554 -0
- package/dist/v1/libs/TinyRadioFm.mjs +1137 -0
- package/dist/v1/libs/TinySmartScroller.cjs +39 -26
- package/dist/v1/libs/TinySmartScroller.d.mts +16 -62
- package/dist/v1/libs/TinySmartScroller.mjs +35 -26
- package/dist/v1/libs/UltraRandomMsgGen.d.mts +7 -7
- package/dist/v1/libs/examples/TinyClassManager.cjs +43 -41
- package/dist/v1/libs/examples/TinyClassManager.mjs +37 -37
- package/docs/v1/README.md +6 -0
- package/docs/v1/basics/mediaContent.md +131 -0
- package/docs/v1/libs/TinyClassManager/TinyPluginInliner.md +107 -0
- package/docs/v1/libs/TinyClassManager.md +69 -70
- package/docs/v1/libs/TinyDragDropDetector.md +5 -9
- package/docs/v1/libs/TinyElementObserver.md +3 -6
- package/docs/v1/libs/TinyEvents.md +1 -1
- package/docs/v1/libs/TinyHtml.md +0 -29
- package/docs/v1/libs/TinyMediaPlayer.md +100 -0
- package/docs/v1/libs/TinyRadioFm.md +141 -0
- package/docs/v1/libs/TinySmartScroller.md +3 -3
- package/package.json +40 -22
|
@@ -10,51 +10,51 @@ class EntityCore {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
// 2. Creating Dependent DLCs
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/** @
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
},
|
|
13
|
+
/** @param {typeof EntityCore} Base */
|
|
14
|
+
const applyHealth = (Base) => class HealthModule extends Base {
|
|
15
|
+
static _tinyDepName = 'Health';
|
|
16
|
+
/** @type {string[]} */
|
|
17
|
+
static _tinyDeps = []; // No dependencies, works just with the Core
|
|
18
|
+
/** @param {string} id */
|
|
19
|
+
constructor(id) {
|
|
20
|
+
super(id);
|
|
21
|
+
this.hp = 100;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Deducts health from the entity.
|
|
25
|
+
* @param {number} amount - The amount of raw damage to deal.
|
|
26
|
+
*/
|
|
27
|
+
takeDamage(amount) {
|
|
28
|
+
this.hp -= amount;
|
|
29
|
+
console.log(`Took ${amount} damage. HP left: ${this.hp}`);
|
|
30
|
+
}
|
|
32
31
|
};
|
|
33
32
|
// 3. Creating Independent DLCs
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
/** @param {ReturnType<typeof applyHealth>} Base */
|
|
34
|
+
const applyArmor = (Base) => class ArmorModule extends Base {
|
|
35
|
+
static _tinyDepName = 'Armor';
|
|
36
|
+
static _tinyDeps = ['Health']; // Strictly requires the Health module to be installed first
|
|
37
|
+
/** @param {string} id */
|
|
38
|
+
constructor(id) {
|
|
39
|
+
super(id);
|
|
40
|
+
this.armor = 50;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Deducts health, factoring in damage mitigation provided by armor.
|
|
44
|
+
* @param {number} amount - The initial damage before armor reduction.
|
|
45
|
+
*/
|
|
46
|
+
takeDamage(amount) {
|
|
44
47
|
/**
|
|
45
|
-
*
|
|
46
|
-
* @param {number} amount - The initial damage before armor reduction.
|
|
48
|
+
* Calculated damage after armor mitigation is applied, ensuring it never drops below 0.
|
|
47
49
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
},
|
|
50
|
+
const reducedDamage = Math.max(0, amount - this.armor * 0.1);
|
|
51
|
+
super.takeDamage(reducedDamage);
|
|
52
|
+
}
|
|
53
53
|
};
|
|
54
54
|
// 4. Building the final class
|
|
55
55
|
try {
|
|
56
56
|
const CoreTest = new TinyClassManager(EntityCore);
|
|
57
|
-
const FullyArmoredEntity = CoreTest.
|
|
57
|
+
const FullyArmoredEntity = CoreTest.insert(applyHealth).insert(applyArmor).build();
|
|
58
58
|
const myEntity = new FullyArmoredEntity('Player_1');
|
|
59
59
|
myEntity.takeDamage(30);
|
|
60
60
|
console.log(`Armor value: ${myEntity.armor}`);
|
package/docs/v1/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Here, you can navigate through different sections of the project. Below are the
|
|
|
20
20
|
- 🖼️ **[Html](./basics/html.md)** — Utilities for handling DOM element interactions like collision detection and basic element manipulation.
|
|
21
21
|
- 📺 **[FullScreen](./basics/fullScreen.md)** — A complete fullscreen API manager with detection, event handling, and cross-browser compatibility.
|
|
22
22
|
- 🧱 **[Collision](./basics/collision.md)** — Full-featured rectangle collision detection system with directional analysis, depth calculation, and center offset metrics.
|
|
23
|
+
- 🎵 **[MediaContent](./basics/mediaContent.md)** — A comprehensive media processing module for downloading audio, extracting rich ID3 metadata, and managing image blobs for album art.
|
|
23
24
|
|
|
24
25
|
### 2. **`libs/`**
|
|
25
26
|
- 🗂️ **[TinyPromiseQueue](./libs/TinyPromiseQueue.md)** — A class that allows sequential execution of asynchronous tasks, supporting task delays, cancellation, and queue management.
|
|
@@ -62,7 +63,12 @@ Here, you can navigate through different sections of the project. Below are the
|
|
|
62
63
|
* 🕒 **[TinyAnalogClock](./libs/TinyAnalogClock.md)** — A lightweight analog clock engine for managing time-based rotations, supporting custom offsets, smooth transitions, and easy binding to CSS variables rendering.
|
|
63
64
|
* 🔍 **[TinyArrayComparator](./libs/TinyArrayComparator.md)** — A lightweight, highly optimized JavaScript utility class designed to compare two arrays and efficiently detect which items were **added** or **deleted**.
|
|
64
65
|
* 🧠 **[TinyMamdaniInferenceSystem](./libs/TinyMamdaniInferenceSystem.md)** — A implementation of a Mamdani Inference System, allowing you to model logic using trapezoidal membership functions.
|
|
66
|
+
* ⏯️ **[TinyMediaPlayer](./libs/TinyMediaPlayer.md)** — A universal media player architecture utilizing an adapter pattern to orchestrate multiple platform APIs, featuring robust playlist management, playback controls, and weighted random selection.
|
|
67
|
+
|
|
68
|
+
### 2.1. **`libs/TinyClassManager`**
|
|
69
|
+
|
|
65
70
|
* ⚙️ **[TinyClassManager](./libs/TinyClassManager.md)** — A lightweight, immutable manager designed to linearly compose a base class with multiple modular plugins, featuring automatic dependency verification and duplicate conflict protection.
|
|
71
|
+
* 🛠️ **[TinyPluginInliner](./libs/TinyClassManager/TinyPluginInliner.md)** — A powerful build-time utility that inlines plugin code into a single bundle, automatically rewriting import paths and hoisting dependencies for seamless deployment.
|
|
66
72
|
|
|
67
73
|
### 3. **`fileManager/`**
|
|
68
74
|
* 📁 **[Main](./fileManager/main.md)** — A Node.js file/directory utility module with support for JSON, backups, renaming, size analysis, and more.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 🎵 Media Content Module
|
|
2
|
+
|
|
3
|
+
Welcome to the documentation for the media content module! This module is the heart of the media processing pipeline, responsible for downloading audio, extracting rich metadata (ID3 tags), managing memory for image blobs, and preparing finalized `MediaContent` objects for the radio system. 🚀
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📦 Data Structures & Types
|
|
8
|
+
|
|
9
|
+
These definitions ensure type safety and provide a clear blueprint for how media information is structured throughout the application.
|
|
10
|
+
|
|
11
|
+
### 🎼 Core Metadata Types
|
|
12
|
+
|
|
13
|
+
| Type | Description |
|
|
14
|
+
| :--- | :--- |
|
|
15
|
+
| `MediaContent` | **The Final Object.** A combination of `MediaContentBase` and `MediaContentMetadata`. This is what the system uses for playback. |
|
|
16
|
+
| `MediaContentBase` | The essential properties: `id`, `title`, `artist`, `duration`, `url`, and `weight`. |
|
|
17
|
+
| `MediaContentMetadata` | Rich metadata modeled after `music-metadata`. Includes `album`, `genre`, `year`, `track` info, etc. |
|
|
18
|
+
| `MediaNumber` | A structure for indexing: `{ no: number \| null, of: number \| null }`. |
|
|
19
|
+
| `UnknownArtistGetter` | A type that can be a `string` or a function returning a `string`. |
|
|
20
|
+
|
|
21
|
+
### 🖼️ Image & Picture Types
|
|
22
|
+
|
|
23
|
+
| Type | Description |
|
|
24
|
+
| :--- | :--- |
|
|
25
|
+
| `IPicture` | Represents an image attachment (like album art). |
|
|
26
|
+
| `IPictureTemplate<T>` | A generic template for pictures, where `T` is the data type (`Uint8Array` or `string`). |
|
|
27
|
+
|
|
28
|
+
### 🚦 Progress & Error Types
|
|
29
|
+
|
|
30
|
+
| Type | Description |
|
|
31
|
+
| :--- | :--- |
|
|
32
|
+
| `LoadingMediaProgress` | Tracks the current state: `status` ('loading' or 'success'), `stage`, and the `url`. |
|
|
33
|
+
| `LoadingProgressStage` | Stages: `INITIALIZING` ➡️ `DOWNLOADING` ➡️ `METADATA_LOADED` ➡️ `EXTRACTING_ID3` ➡️ `COMPLETE`. |
|
|
34
|
+
| `LoadingErrorStage` | Error stages: `INITIALIZING`, `DOWNLOADING`, `METADATA`, `ID3`, or `UNKNOWN`. |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## ❌ Custom Errors
|
|
39
|
+
|
|
40
|
+
### `MediaLoadingError`
|
|
41
|
+
Used to provide specific context when something goes wrong during the media preparation lifecycle.
|
|
42
|
+
|
|
43
|
+
* **Properties:**
|
|
44
|
+
* `message`: Human-readable error description.
|
|
45
|
+
* `url`: The URL that caused the failure.
|
|
46
|
+
* `stage`: The `LoadingErrorStage` where the error occurred.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🛠️ Utility Functions
|
|
51
|
+
|
|
52
|
+
### Factory & Validation
|
|
53
|
+
|
|
54
|
+
| Function | Description |
|
|
55
|
+
| :--- | :--- |
|
|
56
|
+
| `getMediaContentBase` | Returns a fresh, empty `MediaContentBase` object. |
|
|
57
|
+
| `getMediaContentMetadata` | Returns a fresh, empty `MediaContentMetadata` object. |
|
|
58
|
+
| `valMediaContentMetadata` | Performs strict validation on a full `MediaContentMetadata` object. |
|
|
59
|
+
| `valMediaContentMetadataPartial` | Performs validation on a partial metadata object (useful for patches/updates). |
|
|
60
|
+
|
|
61
|
+
### Data & Memory Management
|
|
62
|
+
|
|
63
|
+
| Function | Description |
|
|
64
|
+
| :--- | :--- |
|
|
65
|
+
| `generateSimpleHash` | Generates a deterministic 8-character ID from a string using SHA-1. |
|
|
66
|
+
| `convertToBlobUrl` | Converts `Uint8Array` or Base64 strings into high-performance Blob URLs. It also tracks usage to prevent memory leaks. |
|
|
67
|
+
| `blobUrlToBase64` | Converts a `blob:` URL back into a Base64 Data URL. Useful for exporting data. |
|
|
68
|
+
| `revokeContentUrls` | Safely cleans up memory by revoking Blob URLs associated with a `MediaContent` object. **Call this when media is no longer needed!** 🧹 |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 🚀 Main Operations
|
|
73
|
+
|
|
74
|
+
### `extractMediaId3Tags`
|
|
75
|
+
Downloads an audio file and uses a provided parser to extract its internal metadata.
|
|
76
|
+
|
|
77
|
+
* **Parameters:**
|
|
78
|
+
* `url` (string): The full URL of the audio file.
|
|
79
|
+
* `parseFile` (Function): A callback that accepts a `Blob` and returns a promise with metadata.
|
|
80
|
+
* **Returns:** `Promise<MediaContentMetadata>`
|
|
81
|
+
* **Throws:** `TypeError` for invalid arguments or `Error` for network/parsing failures.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### `parseMediaMetadata`
|
|
86
|
+
**The Primary Factory Method.** This is the main entry point for turning a URL or an Audio element into a fully prepared `MediaContent` object. It handles the heavy lifting: downloading, metadata extraction, fallback logic, and progress reporting.
|
|
87
|
+
|
|
88
|
+
#### 📝 Parameters
|
|
89
|
+
|
|
90
|
+
| Parameter | Type | Description |
|
|
91
|
+
| :--- | :--- | :--- |
|
|
92
|
+
| `source` | `string \| HTMLMediaElement` | The audio source (URL or Audio object). |
|
|
93
|
+
| `defaultMetadata` | `Partial<MediaContent>` | Default data for the metadata. |
|
|
94
|
+
| `metadata` | `Partial<MediaContent>` | Manual overrides for the metadata (highest priority). |
|
|
95
|
+
| `parseFile` | `ParseMediaContentMetadata` | The library function used to parse the file. |
|
|
96
|
+
| `callbacks` | `Object` | Contains `onProgress` and `onError` listeners. |
|
|
97
|
+
| `unknownArtist` | `UnknownArtistGetter` | Logic to determine the artist if none is found. |
|
|
98
|
+
|
|
99
|
+
#### 💡 Usage Examples
|
|
100
|
+
|
|
101
|
+
**1. Using a simple URL with manual overrides:**
|
|
102
|
+
```javascript
|
|
103
|
+
import { parseMediaMetadata } from './mediaContent.mjs';
|
|
104
|
+
import { parseBlob } from 'music-metadata';
|
|
105
|
+
|
|
106
|
+
// Note: We pass empty objects for default/manual if we want to jump straight to the parser
|
|
107
|
+
const track = await parseMediaMetadata(
|
|
108
|
+
'/assets/song.mp3',
|
|
109
|
+
{},
|
|
110
|
+
{ title: 'Manual Override' },
|
|
111
|
+
parseBlob
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
console.log(track.title); // "Manual Override"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**2. Using an existing Audio object with progress tracking:**
|
|
118
|
+
```javascript
|
|
119
|
+
const audio = new Audio('/assets/song.mp3');
|
|
120
|
+
|
|
121
|
+
const track = await parseMediaMetadata(
|
|
122
|
+
audio,
|
|
123
|
+
{},
|
|
124
|
+
{},
|
|
125
|
+
parseBlob,
|
|
126
|
+
{
|
|
127
|
+
onProgress: (p) => console.log(`[${p.stage}] ${p.status}`),
|
|
128
|
+
onError: (e) => console.error(`Error at ${e.stage}: ${e.error.message}`)
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 🛠️ TinyPluginInliner
|
|
2
|
+
|
|
3
|
+
`TinyPluginInliner` is a powerful **build-time utility** designed specifically to complement the `TinyClassManager`.
|
|
4
|
+
|
|
5
|
+
When using `TinyClassManager`, you compose classes using a mixin pattern via the `.insert()` method. While this is incredibly flexible for development, it creates a challenge for deployment: you end up with many small, interconnected files and complex import paths.
|
|
6
|
+
|
|
7
|
+
**`TinyPluginInliner` solves this by "inlining" your plugins directly into a single, self-contained bundle.** It automates the process of fetching plugin code, rewriting their internal import paths, and hoisting dependencies to the top of your final file. 🚀
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ✨ Key Features
|
|
12
|
+
|
|
13
|
+
* **📦 Automatic Inlining:** It scans your entry file for `.insert(PluginName)` calls and replaces them with the actual source code of the plugin.
|
|
14
|
+
* **🔗 Smart Path Rewriting:** It automatically corrects `import` statements inside your plugins so they point to the correct relative locations in your `dist` folder.
|
|
15
|
+
* **📚 JSDoc Support:** It doesn't just fix code; it also rewrites `import()` calls and `@typedef` paths inside JSDoc comments, ensuring your IDE (like VS Code) keeps providing perfect autocomplete in the bundled version.
|
|
16
|
+
* **🌐 NPM Integration:** It can reach into your `node_modules`, grab an NPM package used by a plugin, and inline it into your bundle, even fixing that package's internal relative paths!
|
|
17
|
+
* **🔄 Root Remapping:** Through `rootReplacement`, you can tell the inliner to automatically swap directory names (e.g., changing all `src/` imports to `dist/` imports).
|
|
18
|
+
* **🔝 Dependency Hoisting:** It collects all necessary imports and type definitions from all plugins and moves them to the very top of the generated file for a clean, valid structure.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ⚙️ Configuration
|
|
23
|
+
|
|
24
|
+
When creating an instance of `TinyPluginInliner`, you provide an `InlinerConfig` object:
|
|
25
|
+
|
|
26
|
+
| Property | Type | Description |
|
|
27
|
+
| :--- | :--- | :--- |
|
|
28
|
+
| `entryPoint` | `string` | The absolute or relative path to your main file (where you call `.insert()`). |
|
|
29
|
+
| `outDir` | `string` | The directory where the final bundle will be saved. |
|
|
30
|
+
| `outFileName` | `string` | The name of the generated bundle file (e.g., `bundle.mjs`). |
|
|
31
|
+
| `rootReplacement` | `Object` | *(Optional)* An object `{ from: string, to: string }` to remap project roots. |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 How to Use
|
|
36
|
+
|
|
37
|
+
### 1. Develop your Plugins
|
|
38
|
+
Your plugins must follow the `TinyClassManager` requirement of having static properties for identification.
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
// src/plugins/LoggerPlugin.mjs
|
|
42
|
+
export class LoggerPlugin {
|
|
43
|
+
static _tinyDepName = 'Logger';
|
|
44
|
+
static _tinyDeps = [];
|
|
45
|
+
|
|
46
|
+
constructor(Base) {
|
|
47
|
+
super();
|
|
48
|
+
this.log = (msg) => console.log(`[LOG]: ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Create your Entry Point
|
|
54
|
+
This is the file that uses the `TinyClassManager` to compose your final class.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
// src/main.mjs
|
|
58
|
+
import TinyClassManager from './libs/TinyClassManager.mjs';
|
|
59
|
+
import { LoggerPlugin } from './plugins/LoggerPlugin.mjs';
|
|
60
|
+
|
|
61
|
+
class BaseUser {}
|
|
62
|
+
|
|
63
|
+
// The Inliner will look for this specific pattern!
|
|
64
|
+
const User = new TinyClassManager(BaseUser)
|
|
65
|
+
.insert(LoggerPlugin)
|
|
66
|
+
.build();
|
|
67
|
+
|
|
68
|
+
export default User;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Run the Inliner
|
|
72
|
+
Create a build script (e.g., `build.mjs`) to trigger the process.
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
import TinyPluginInliner from './libs/TinyClassManager/TinyPluginInliner.mjs';
|
|
76
|
+
|
|
77
|
+
const builder = new TinyPluginInliner({
|
|
78
|
+
entryPoint: 'src/main.mjs',
|
|
79
|
+
outDir: 'dist',
|
|
80
|
+
outFileName: 'app.bundle.mjs',
|
|
81
|
+
// This ensures that if a plugin imports from 'src/utils',
|
|
82
|
+
// it will be rewritten to 'dist/utils' in the bundle.
|
|
83
|
+
rootReplacement: {
|
|
84
|
+
from: 'src',
|
|
85
|
+
to: 'dist',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
builder.build();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🔄 Before vs. After
|
|
95
|
+
|
|
96
|
+
### 📝 Before (Development)
|
|
97
|
+
Your project is a web of files:
|
|
98
|
+
* `src/main.mjs` $\rightarrow$ imports `LoggerPlugin`
|
|
99
|
+
* `src/plugins/LoggerPlugin.mjs` $\rightarrow$ imports `src/utils/helper.mjs`
|
|
100
|
+
|
|
101
|
+
### 📦 After (Production Bundle)
|
|
102
|
+
The `dist/app.bundle.mjs` is a single, massive, but perfectly valid file:
|
|
103
|
+
1. **Top:** All imports from `main.mjs`, `LoggerPlugin.mjs`, and `helper.mjs` are hoisted here.
|
|
104
|
+
2. **Middle:** All `@typedef` definitions are gathered here.
|
|
105
|
+
3. **Bottom:** The logic of `main.mjs` runs, but the `.insert(LoggerPlugin)` call has been replaced by the actual code of the `LoggerPlugin` class, which now correctly references the hoisted helper functions.
|
|
106
|
+
|
|
107
|
+
**Everything is ready to be shipped!** 🚢💨
|
|
@@ -6,9 +6,10 @@ Welcome to **TinyClassManager**! This is a lightweight, linear mixin manager des
|
|
|
6
6
|
|
|
7
7
|
## 🚀 Key Features
|
|
8
8
|
|
|
9
|
-
* **🔒 Immutable Chain State:** Each `.
|
|
9
|
+
* **🔒 Immutable Chain State:** Each `.insert()` call consumes the previous manager, preventing unwanted side effects or weird mutations.
|
|
10
10
|
* **🧩 Dependency Verification:** Automatically ensures that a required plugin is loaded before the dependent plugin is applied.
|
|
11
11
|
* **🛑 Conflict Protection:** Prevents you from installing the exact same plugin twice on the same class chain.
|
|
12
|
+
* **⚡ Clean Architecture:** Uses static class properties to manage metadata, keeping your plugin functions pure and direct.
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
@@ -25,8 +26,9 @@ Welcome to **TinyClassManager**! This is a lightweight, linear mixin manager des
|
|
|
25
26
|
|
|
26
27
|
#### Methods
|
|
27
28
|
|
|
28
|
-
* **`constructor(coreClass)`**:
|
|
29
|
-
* **`
|
|
29
|
+
* **`constructor(coreClass)`**: Initializes the manager with your foundational core class.
|
|
30
|
+
* **`insert(applyFn)`**: Applies a new plugin to the chain by passing a function that returns the extended class. The returned class **must** define a `static _tinyDepName` string, and can optionally define a `static _tinyDeps` array of strings. Returns a *new* `TinyClassManager` instance.
|
|
31
|
+
* **`use(plugin)`**: *[Deprecated]* Legacy method for applying plugins using a definition object. Use `insert` instead.
|
|
30
32
|
* **`build()`**: Finalizes the composition chain, marks the instance as consumed, and returns the fully compiled class ready for instantiation.
|
|
31
33
|
|
|
32
34
|
---
|
|
@@ -57,84 +59,81 @@ class EntityCore {
|
|
|
57
59
|
|
|
58
60
|
### Step 2: Create an Independent Module 🏷️
|
|
59
61
|
|
|
60
|
-
Now, let's create our first module. A plugin is
|
|
62
|
+
Now, let's create our first module. A plugin is a function that receives the base class and returns an extended version of it. We use static properties (`_tinyDepName` and `_tinyDeps`) to tell the manager what this plugin is and what it needs. This module doesn't depend on anything else, so its dependencies array is empty.
|
|
61
63
|
|
|
62
64
|
```javascript
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
65
|
+
/**
|
|
66
|
+
* @param {typeof EntityCore} Base - The incoming base class to extend.
|
|
67
|
+
*/
|
|
68
|
+
const applyHealth = (Base) =>
|
|
69
|
+
class HealthModule extends Base {
|
|
70
|
+
static _tinyDepName = 'Health';
|
|
71
|
+
/** @type {string[]} */
|
|
72
|
+
static _tinyDeps = []; // Works fine straight out of the box with the Core
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} id
|
|
76
|
+
*/
|
|
77
|
+
constructor(id) {
|
|
78
|
+
super(id); // Always remember to pass arguments down!
|
|
79
|
+
|
|
80
|
+
/** @type {number} */
|
|
81
|
+
this.hp = 100;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Deducts health from the entity.
|
|
86
|
+
* @param {number} amount - The amount of raw damage to deal.
|
|
87
|
+
*/
|
|
88
|
+
takeDamage(amount) {
|
|
89
|
+
this.hp -= amount;
|
|
90
|
+
console.log(`Took ${amount} damage. HP left: ${this.hp}`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
92
93
|
|
|
93
94
|
```
|
|
94
95
|
|
|
95
96
|
### Step 3: Create a Dependent Module 🛡️
|
|
96
97
|
|
|
97
|
-
Here is where the magic happens! The `Armor` module strictly requires the `Health` module to be loaded first because it relies on overriding the `takeDamage` logic. We enforce this relationship inside the `
|
|
98
|
+
Here is where the magic happens! The `Armor` module strictly requires the `Health` module to be loaded first because it relies on overriding the `takeDamage` logic. We enforce this relationship inside the `static _tinyDeps` array.
|
|
98
99
|
|
|
99
100
|
```javascript
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
},
|
|
131
|
-
};
|
|
101
|
+
/**
|
|
102
|
+
* @param {ReturnType<typeof applyHealth>} Base - The class containing Health features.
|
|
103
|
+
*/
|
|
104
|
+
const applyArmor = (Base) =>
|
|
105
|
+
class ArmorModule extends Base {
|
|
106
|
+
static _tinyDepName = 'Armor';
|
|
107
|
+
static _tinyDeps = ['Health']; // Strictly requires the "Health" module!
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {string} id
|
|
111
|
+
*/
|
|
112
|
+
constructor(id) {
|
|
113
|
+
super(id);
|
|
114
|
+
|
|
115
|
+
/** @type {number} */
|
|
116
|
+
this.armor = 50;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Deducts health, factoring in damage mitigation provided by armor.
|
|
121
|
+
* @param {number} amount - The initial damage before armor reduction.
|
|
122
|
+
*/
|
|
123
|
+
takeDamage(amount) {
|
|
124
|
+
// Mitigates damage using the armor stat
|
|
125
|
+
const reducedDamage = Math.max(0, amount - this.armor * 0.1);
|
|
126
|
+
|
|
127
|
+
// Pass the mitigated damage up to the Health layer
|
|
128
|
+
super.takeDamage(reducedDamage);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
132
131
|
|
|
133
132
|
```
|
|
134
133
|
|
|
135
134
|
### Step 4: Chain and Build Your Custom Class 🏗️
|
|
136
135
|
|
|
137
|
-
Finally, use the manager to safely piece your modules together. Remember that `TinyClassManager` uses an explosive state pattern: once you run `.
|
|
136
|
+
Finally, use the manager to safely piece your modules together. Remember that `TinyClassManager` uses an explosive state pattern: once you run `.insert()` or `.build()`, that specific instance is closed, forcing you to use the new returned chain instance.
|
|
138
137
|
|
|
139
138
|
```javascript
|
|
140
139
|
try {
|
|
@@ -142,8 +141,8 @@ try {
|
|
|
142
141
|
const CoreTest = new TinyClassManager(EntityCore);
|
|
143
142
|
|
|
144
143
|
// 2. Chain your modules and compile!
|
|
145
|
-
// If you try to swap the order (.
|
|
146
|
-
const FullyArmoredEntity = CoreTest.
|
|
144
|
+
// If you try to swap the order (.insert(applyArmor).insert(applyHealth)), it will throw an explicit dependency error.
|
|
145
|
+
const FullyArmoredEntity = CoreTest.insert(applyHealth).insert(applyArmor).build();
|
|
147
146
|
|
|
148
147
|
// 3. Create your custom instance!
|
|
149
148
|
const myEntity = new FullyArmoredEntity('Player_1');
|
|
@@ -170,5 +169,5 @@ try {
|
|
|
170
169
|
When you want to build a new set of mechanics for your game or application using this architecture, always remember to follow this loop:
|
|
171
170
|
|
|
172
171
|
1. **Define your Base State**: Keep it clean and focused on global data configurations.
|
|
173
|
-
2. **Declare Plugins as
|
|
172
|
+
2. **Declare Plugins as Functions**: Create a function that accepts a `Base` class and returns an extended class. Equip it with `static _tinyDepName` (string) and `static _tinyDeps` (array of strings).
|
|
174
173
|
3. **Use `super` Inheritances**: Inside your plugin classes, always call `super(...)` in constructors and use `super.methodName(...)` if you are overriding an existing skill or function to allow all mixed behaviors to fire smoothly down the pipeline!
|
|
@@ -21,7 +21,6 @@ A lightweight JavaScript utility for detecting drag-and-drop file operations on
|
|
|
21
21
|
import TinyDragDropDetector from './TinyDragDropDetector.js';
|
|
22
22
|
|
|
23
23
|
const dnd = new TinyDragDropDetector({
|
|
24
|
-
fullscreen: false,
|
|
25
24
|
target: document.getElementById('drop-area'),
|
|
26
25
|
hoverClass: 'hover-effect',
|
|
27
26
|
onDrop: (files, event) => {
|
|
@@ -50,19 +49,19 @@ A drag-and-drop detector instance.
|
|
|
50
49
|
### Constructor
|
|
51
50
|
|
|
52
51
|
```javascript
|
|
53
|
-
new TinyDragDropDetector(options)
|
|
52
|
+
new TinyDragDropDetector(target, options)
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|
#### Parameters:
|
|
57
56
|
|
|
58
|
-
| Name | Type |
|
|
59
|
-
| --------- | ------------------------------------------- |
|
|
60
|
-
| `
|
|
57
|
+
| Name | Type | Description |
|
|
58
|
+
| --------- | ------------------------------------------- | ------------------------------------------------------- |
|
|
59
|
+
| `target` | `HTMLElement` | The DOM element where drag listeners will be attached. |
|
|
60
|
+
| `options` | [`DragAndDropOptions`](#draganddropoptions) | Configuration object. |
|
|
61
61
|
|
|
62
62
|
#### Throws:
|
|
63
63
|
|
|
64
64
|
* `TypeError` if `target` is not an `HTMLElement`.
|
|
65
|
-
* `TypeError` if `fullscreen` is not a boolean.
|
|
66
65
|
* `TypeError` if `hoverClass` is not a string.
|
|
67
66
|
* `TypeError` if `onDrop` is not a function.
|
|
68
67
|
* `TypeError` if `onEnter` is defined but not a function.
|
|
@@ -76,7 +75,6 @@ new TinyDragDropDetector(options)
|
|
|
76
75
|
| ----------------- | ------------- | ------------------------------------------------------------------------ |
|
|
77
76
|
| `getTarget()` | `HTMLElement` | Returns the DOM element where listeners are attached. |
|
|
78
77
|
| `getHoverClass()` | `string` | Returns the CSS class applied during drag hover. |
|
|
79
|
-
| `isFullScreen()` | `boolean` | Indicates whether the detector is in fullscreen mode (`document.body`). |
|
|
80
78
|
| `isDragging()` | `boolean` | Returns whether a drag operation is currently active over the target. |
|
|
81
79
|
| `bound()` | `boolean` | Returns whether the event listeners are currently bound to the target. |
|
|
82
80
|
| `destroy()` | `void` | Destroys the detector, unbinding all events and cleaning up CSS classes. |
|
|
@@ -98,8 +96,6 @@ new TinyDragDropDetector(options)
|
|
|
98
96
|
```javascript
|
|
99
97
|
/**
|
|
100
98
|
* @typedef {Object} DragAndDropOptions
|
|
101
|
-
* @property {HTMLElement} [target=document.body] - The DOM element where drag listeners will be attached. Defaults to `document.body` if `fullscreen` is true or no target is provided.
|
|
102
|
-
* @property {boolean} [fullscreen=true] - If true, listeners are attached to the entire page (`document.body`). If false, the `target` must be specified.
|
|
103
99
|
* @property {string} [hoverClass="dnd-hover"] - CSS class applied to the target element while files are being dragged over it.
|
|
104
100
|
* @property {(files: FileList, event: DragEvent) => void} onDrop - Callback function executed when files are dropped onto the target.
|
|
105
101
|
* @property {(event: DragEvent) => void} [onEnter] - Optional callback triggered when dragging enters the target area.
|
|
@@ -22,14 +22,13 @@ type ElementDetectorsFn = (
|
|
|
22
22
|
## 🔹 Constructor
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
|
-
new TinyElementObserver({
|
|
26
|
-
el?: Element,
|
|
25
|
+
new TinyElementObserver(el, {
|
|
27
26
|
initDetectors?: Array<[string, ElementDetectorsFn]>,
|
|
28
27
|
initCfg?: MutationObserverInit
|
|
29
28
|
})
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
* **`el`**
|
|
31
|
+
* **`el`** → Initial element to observe 🏷️
|
|
33
32
|
* **`initDetectors`** *(optional)* → Array of detectors to register on creation 🎛️
|
|
34
33
|
* **`initCfg`** *(optional)* → Initial `MutationObserverInit` configuration ⚙️
|
|
35
34
|
|
|
@@ -39,7 +38,6 @@ new TinyElementObserver({
|
|
|
39
38
|
|
|
40
39
|
| Property | Type | Description |
|
|
41
40
|
| ----------- | ------------------------------------- | --------------------------------------------------------- |
|
|
42
|
-
| `el` | `Element \| undefined` | The DOM element being observed. Can only be set once. 🏗️ |
|
|
43
41
|
| `settings` | `MutationObserverInit` | Observer configuration object. 🔧 |
|
|
44
42
|
| `observer` | `MutationObserver \| null` | Internal observer instance. |
|
|
45
43
|
| `detectors` | `Array<[string, ElementDetectorsFn]>` | List of registered detectors. |
|
|
@@ -85,8 +83,7 @@ const logDetector = (mutation, index, mutations) => {
|
|
|
85
83
|
};
|
|
86
84
|
|
|
87
85
|
// Create instance
|
|
88
|
-
const observer = new TinyElementObserver({
|
|
89
|
-
el: document.querySelector('#target'),
|
|
86
|
+
const observer = new TinyElementObserver(document.querySelector('#target'), {
|
|
90
87
|
initDetectors: [['logger', logDetector]],
|
|
91
88
|
initCfg: { childList: true, subtree: true }
|
|
92
89
|
});
|
|
@@ -111,7 +111,7 @@ Removes a specific listener from an event.
|
|
|
111
111
|
|
|
112
112
|
Removes all listeners for one or more events.
|
|
113
113
|
|
|
114
|
-
#### `offAllTypes(): void`
|
|
114
|
+
#### `offAllTypes(): void` and `removeAllListeners(): void`
|
|
115
115
|
|
|
116
116
|
Removes **all listeners** for **all events**.
|
|
117
117
|
|