This commit is contained in:
Rockwell Schrock
2025-12-19 13:48:55 -05:00
parent 593c22ebc4
commit 716afa4ec2
7 changed files with 199 additions and 105 deletions
+87 -7
View File
@@ -1,9 +1,56 @@
// src/index.js
var _DEFAULT_MODEL_NAME = "og_plus";
var _API_CACHE_KEY = "trmnl-picker-api-cache";
var _CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
var TRMNLPicker = class _TRMNLPicker {
static API_BASE_URL = "https://usetrmnl.com";
/**
* Get cached API response from localStorage
* @private
* @static
* @returns {{models: Array, palettes: Array} | null} Cached data or null if expired/missing
*/
static _getCachedApiData() {
try {
const cached = localStorage.getItem(_API_CACHE_KEY);
if (!cached)
return null;
const { timestamp, models, palettes } = JSON.parse(cached);
const now = Date.now();
if (now - timestamp > _CACHE_TTL_MS) {
localStorage.removeItem(_API_CACHE_KEY);
return null;
}
return { models, palettes };
} catch (error) {
console.warn("TRMNLPicker: Failed to read API cache:", error);
return null;
}
}
/**
* Save API response to localStorage cache
* @private
* @static
* @param {Array} models - Models array
* @param {Array} palettes - Palettes array
*/
static _setCachedApiData(models, palettes) {
try {
const cacheData = {
timestamp: Date.now(),
models,
palettes
};
localStorage.setItem(_API_CACHE_KEY, JSON.stringify(cacheData));
} catch (error) {
console.warn("TRMNLPicker: Failed to save API cache:", error);
}
}
/**
* Create a TRMNLPicker instance, fetching data from TRMNL API if not provided
*
* Automatically caches API responses in localStorage for 24 hours to reduce network requests.
*
* @static
* @param {string|Element} formIdOrElement - Form element ID or DOM element
* @param {Object} options - Configuration options
@@ -14,7 +61,7 @@ var TRMNLPicker = class _TRMNLPicker {
* @throws {Error} If API fetch fails when models or palettes are not provided
*
* @example
* // Fetch models and palettes from API
* // Fetch models and palettes from API (or use cached data if available)
* const picker = await TRMNLPicker.create('screen-picker')
*
* // Provide your own data
@@ -22,6 +69,13 @@ var TRMNLPicker = class _TRMNLPicker {
*/
static async create(formId, options = {}) {
let { models, palettes, localStorageKey } = options;
if (!models && !palettes) {
const cached = _TRMNLPicker._getCachedApiData();
if (cached) {
models = cached.models;
palettes = cached.palettes;
}
}
if (!models) {
try {
const response = await fetch(`${_TRMNLPicker.API_BASE_URL}/api/models`);
@@ -46,6 +100,9 @@ var TRMNLPicker = class _TRMNLPicker {
throw new Error(`TRMNLPicker: Failed to fetch palettes from API: ${error.message}`);
}
}
if (!options.models && !options.palettes) {
_TRMNLPicker._setCachedApiData(models, palettes);
}
return new _TRMNLPicker(formId, { models, palettes, localStorageKey });
}
constructor(formIdOrElement, options = {}) {
@@ -245,7 +302,6 @@ var TRMNLPicker = class _TRMNLPicker {
const event = new CustomEvent("trmnl:change", {
detail: {
origin,
screenClasses: this.screenClasses,
...this.state
},
bubbles: true
@@ -353,7 +409,7 @@ var TRMNLPicker = class _TRMNLPicker {
}
/**
* Get CSS classes for the current picker configuration
* @public
* @private
* @returns {Array<string>} Array of CSS class names for Framework CSS rendering
*
* Generated classes (in order):
@@ -369,7 +425,7 @@ var TRMNLPicker = class _TRMNLPicker {
* const classes = picker.screenClasses
* // ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x']
*/
get screenClasses() {
get _screenClasses() {
const model = this._state.model;
const palette = this._state.palette;
if (!model) {
@@ -507,7 +563,10 @@ var TRMNLPicker = class _TRMNLPicker {
* model: Object,
* palette: Object,
* isPortrait: boolean,
* isDarkMode: boolean
* isDarkMode: boolean,
* screenClasses: Array<string>,
* width: number,
* height: number
* }} State object containing model (full model object from API), palette (full palette object from API), isPortrait flag, and isDarkMode flag
*
* @example
@@ -516,11 +575,32 @@ var TRMNLPicker = class _TRMNLPicker {
* // model: { name: 'og_plus', label: 'OG+', width: 800, height: 480, ... },
* // palette: { id: '123', name: 'Black', framework_class: 'screen--1bit', ... },
* // isPortrait: false,
* // isDarkMode: false
* // isDarkMode: false,
* // screenClasses: ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x'],
* // width: 800,
* // height: 480
* // }
*/
get state() {
return { ...this._state };
return {
screenClasses: this._screenClasses,
...this._dimensions,
...this._state
};
}
/**
* Get current dimensions (width and height) of the screen in pixels
* @private
* @returns {{ width: number, height: number }} Object with width and height properties
*/
get _dimensions() {
const model = this._state.model;
let width = model.width / model.scale_factor;
let height = model.height / model.scale_factor;
if (this._state.isPortrait) {
[width, height] = [height, width];
}
return { width, height };
}
/**
* Clean up event listeners and references
+2 -2
View File
File diff suppressed because one or more lines are too long
+87 -7
View File
@@ -23,10 +23,57 @@ var TRMNLPicker = (() => {
default: () => src_default
});
var _DEFAULT_MODEL_NAME = "og_plus";
var _API_CACHE_KEY = "trmnl-picker-api-cache";
var _CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
var TRMNLPicker = class _TRMNLPicker {
static API_BASE_URL = "https://usetrmnl.com";
/**
* Get cached API response from localStorage
* @private
* @static
* @returns {{models: Array, palettes: Array} | null} Cached data or null if expired/missing
*/
static _getCachedApiData() {
try {
const cached = localStorage.getItem(_API_CACHE_KEY);
if (!cached)
return null;
const { timestamp, models, palettes } = JSON.parse(cached);
const now = Date.now();
if (now - timestamp > _CACHE_TTL_MS) {
localStorage.removeItem(_API_CACHE_KEY);
return null;
}
return { models, palettes };
} catch (error) {
console.warn("TRMNLPicker: Failed to read API cache:", error);
return null;
}
}
/**
* Save API response to localStorage cache
* @private
* @static
* @param {Array} models - Models array
* @param {Array} palettes - Palettes array
*/
static _setCachedApiData(models, palettes) {
try {
const cacheData = {
timestamp: Date.now(),
models,
palettes
};
localStorage.setItem(_API_CACHE_KEY, JSON.stringify(cacheData));
} catch (error) {
console.warn("TRMNLPicker: Failed to save API cache:", error);
}
}
/**
* Create a TRMNLPicker instance, fetching data from TRMNL API if not provided
*
* Automatically caches API responses in localStorage for 24 hours to reduce network requests.
*
* @static
* @param {string|Element} formIdOrElement - Form element ID or DOM element
* @param {Object} options - Configuration options
@@ -37,7 +84,7 @@ var TRMNLPicker = (() => {
* @throws {Error} If API fetch fails when models or palettes are not provided
*
* @example
* // Fetch models and palettes from API
* // Fetch models and palettes from API (or use cached data if available)
* const picker = await TRMNLPicker.create('screen-picker')
*
* // Provide your own data
@@ -45,6 +92,13 @@ var TRMNLPicker = (() => {
*/
static async create(formId, options = {}) {
let { models, palettes, localStorageKey } = options;
if (!models && !palettes) {
const cached = _TRMNLPicker._getCachedApiData();
if (cached) {
models = cached.models;
palettes = cached.palettes;
}
}
if (!models) {
try {
const response = await fetch(`${_TRMNLPicker.API_BASE_URL}/api/models`);
@@ -69,6 +123,9 @@ var TRMNLPicker = (() => {
throw new Error(`TRMNLPicker: Failed to fetch palettes from API: ${error.message}`);
}
}
if (!options.models && !options.palettes) {
_TRMNLPicker._setCachedApiData(models, palettes);
}
return new _TRMNLPicker(formId, { models, palettes, localStorageKey });
}
constructor(formIdOrElement, options = {}) {
@@ -268,7 +325,6 @@ var TRMNLPicker = (() => {
const event = new CustomEvent("trmnl:change", {
detail: {
origin,
screenClasses: this.screenClasses,
...this.state
},
bubbles: true
@@ -376,7 +432,7 @@ var TRMNLPicker = (() => {
}
/**
* Get CSS classes for the current picker configuration
* @public
* @private
* @returns {Array<string>} Array of CSS class names for Framework CSS rendering
*
* Generated classes (in order):
@@ -392,7 +448,7 @@ var TRMNLPicker = (() => {
* const classes = picker.screenClasses
* // ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x']
*/
get screenClasses() {
get _screenClasses() {
const model = this._state.model;
const palette = this._state.palette;
if (!model) {
@@ -530,7 +586,10 @@ var TRMNLPicker = (() => {
* model: Object,
* palette: Object,
* isPortrait: boolean,
* isDarkMode: boolean
* isDarkMode: boolean,
* screenClasses: Array<string>,
* width: number,
* height: number
* }} State object containing model (full model object from API), palette (full palette object from API), isPortrait flag, and isDarkMode flag
*
* @example
@@ -539,11 +598,32 @@ var TRMNLPicker = (() => {
* // model: { name: 'og_plus', label: 'OG+', width: 800, height: 480, ... },
* // palette: { id: '123', name: 'Black', framework_class: 'screen--1bit', ... },
* // isPortrait: false,
* // isDarkMode: false
* // isDarkMode: false,
* // screenClasses: ['screen', 'screen--1bit', 'screen--v2', 'screen--md', 'screen--1x'],
* // width: 800,
* // height: 480
* // }
*/
get state() {
return { ...this._state };
return {
screenClasses: this._screenClasses,
...this._dimensions,
...this._state
};
}
/**
* Get current dimensions (width and height) of the screen in pixels
* @private
* @returns {{ width: number, height: number }} Object with width and height properties
*/
get _dimensions() {
const model = this._state.model;
let width = model.width / model.scale_factor;
let height = model.height / model.scale_factor;
if (this._state.isPortrait) {
[width, height] = [height, width];
}
return { width, height };
}
/**
* Clean up event listeners and references
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+17 -83
View File
@@ -62,12 +62,6 @@
<ul class='list-reset py1-ul pl1'>
<li class='h5'><span>Instance members</span></li>
<li><a
href='#trmnlpickerscreenclasses'
class='regular pre-open'>
#screenClasses
</a></li>
<li><a
href='#trmnlpickerparams'
class='regular pre-open'>
@@ -184,6 +178,16 @@
<p>Dark mode flag</p>
</li>
<li><code>detail.width</code> <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number">number</a>
<p>Current width of the screen in pixels</p>
</li>
<li><code>detail.height</code> <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number">number</a>
<p>Current height of the screen in pixels</p>
</li>
</ul>
</div>
@@ -387,6 +391,7 @@ picker.<span class="hljs-property">formElement</span>.<span class="hljs-title fu
<p>Create a TRMNLPicker instance, fetching data from TRMNL API if not provided</p>
<p>Automatically caches API responses in localStorage for 24 hours to reduce network requests.</p>
<div class='pre p1 fill-light mt0'>create(formId: any, options: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>, formIdOrElement: (<a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String">string</a> | <a href="https://developer.mozilla.org/docs/Web/API/Element">Element</a>)): <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a>&#x3C;<a href="#trmnlpicker">TRMNLPicker</a>></div>
@@ -504,7 +509,7 @@ picker.<span class="hljs-property">formElement</span>.<span class="hljs-title fu
<div class='py1 quiet mt1 prose-big'>Example</div>
<pre class='p1 overflow-auto round fill-light'><span class="hljs-comment">// Fetch models and palettes from API</span>
<pre class='p1 overflow-auto round fill-light'><span class="hljs-comment">// Fetch models and palettes from API (or use cached data if available)</span>
<span class="hljs-keyword">const</span> picker = <span class="hljs-keyword">await</span> <span class="hljs-title class_">TRMNLPicker</span>.<span class="hljs-title function_">create</span>(<span class="hljs-string">&#x27;screen-picker&#x27;</span>)
<span class="hljs-comment">// Provide your own data</span>
@@ -532,80 +537,6 @@ picker.<span class="hljs-property">formElement</span>.<span class="hljs-title fu
<div class='py1 quiet mt1 prose-big'>Instance Members</div>
<div class="clearfix">
<div class='border-bottom' id='trmnlpickerscreenclasses'>
<div class="clearfix small pointer toggle-sibling">
<div class="py1 contain">
<a class='icon pin-right py1 dark-link caret-right'></a>
<span class='code strong strong truncate'>screenClasses</span>
</div>
</div>
<div class='clearfix toggle-target'>
<section class='p2 mb2 clearfix bg-white minishadow'>
<p>Get CSS classes for the current picker configuration</p>
<div class='pre p1 fill-light mt0'>screenClasses</div>
<div class='py1 quiet mt1 prose-big'>Returns</div>
<code><a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array">Array</a>&#x3C;<a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String">string</a>></code>:
Array of CSS class names for Framework CSS rendering
<p>Generated classes (in order):</p>
<ol>
<li>'screen' - Base class (always present)</li>
<li>palette.framework_class - From selected palette (e.g., 'screen--1bit')</li>
<li>model.css.classes.device - From model API (e.g., 'screen--v2')</li>
<li>model.css.classes.size - From model API (e.g., 'screen--md')</li>
<li>'screen--portrait' - Only when portrait orientation is enabled</li>
<li>'screen--1x' - Scale indicator (always 1x)</li>
<li>'screen--dark-mode' - Only when dark mode is enabled</li>
</ol>
<div class='py1 quiet mt1 prose-big'>Example</div>
<pre class='p1 overflow-auto round fill-light'><span class="hljs-keyword">const</span> classes = picker.<span class="hljs-property">screenClasses</span>
<span class="hljs-comment">// [&#x27;screen&#x27;, &#x27;screen--1bit&#x27;, &#x27;screen--v2&#x27;, &#x27;screen--md&#x27;, &#x27;screen--1x&#x27;]</span></pre>
</section>
</div>
</div>
<div class='border-bottom' id='trmnlpickerparams'>
<div class="clearfix small pointer toggle-sibling">
<div class="py1 contain">
@@ -868,7 +799,7 @@ picker.<span class="hljs-title function_">setParams</span>({
<div class='py1 quiet mt1 prose-big'>Returns</div>
<code>{model: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>, palette: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>, isPortrait: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean">boolean</a>, isDarkMode: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean">boolean</a>}</code>:
<code>{model: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>, palette: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a>, isPortrait: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean">boolean</a>, isDarkMode: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean">boolean</a>, screenClasses: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array">Array</a>&#x3C;<a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String">string</a>>, width: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number">number</a>, height: <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number">number</a>}</code>:
State object containing model (full model object from API), palette (full palette object from API), isPortrait flag, and isDarkMode flag
@@ -888,7 +819,10 @@ picker.<span class="hljs-title function_">setParams</span>({
<span class="hljs-comment">// model: { name: &#x27;og_plus&#x27;, label: &#x27;OG+&#x27;, width: 800, height: 480, ... },</span>
<span class="hljs-comment">// palette: { id: &#x27;123&#x27;, name: &#x27;Black&#x27;, framework_class: &#x27;screen--1bit&#x27;, ... },</span>
<span class="hljs-comment">// isPortrait: false,</span>
<span class="hljs-comment">// isDarkMode: false</span>
<span class="hljs-comment">// isDarkMode: false,</span>
<span class="hljs-comment">// screenClasses: [&#x27;screen&#x27;, &#x27;screen--1bit&#x27;, &#x27;screen--v2&#x27;, &#x27;screen--md&#x27;, &#x27;screen--1x&#x27;],</span>
<span class="hljs-comment">// width: 800,</span>
<span class="hljs-comment">// height: 480</span>
<span class="hljs-comment">// }</span></pre>