retold-content-system 1.0.12 → 1.0.13
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/README.md +130 -68
- package/package.json +3 -3
- package/source/cli/ContentSystem-Server-Setup.js +7 -3
- package/web-application/js/pict.min.js +2 -2
- package/web-application/retold-content-system.compatible.js +50 -7
- package/web-application/retold-content-system.compatible.js.map +1 -1
- package/web-application/retold-content-system.compatible.min.js +5 -5
- package/web-application/retold-content-system.compatible.min.js.map +1 -1
- package/web-application/retold-content-system.js +49 -6
- package/web-application/retold-content-system.js.map +1 -1
- package/web-application/retold-content-system.min.js +1 -1
- package/web-application/retold-content-system.min.js.map +1 -1
|
@@ -2781,18 +2781,45 @@ let tmpCached=tmpBrowseProvider.getChildFolders(pPath);if(!tmpCached){if(typeof
|
|
|
2781
2781
|
* Get the current location from state.
|
|
2782
2782
|
*
|
|
2783
2783
|
* @returns {string} The current location path
|
|
2784
|
-
*/getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserBrowseTree;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":88}],72:[function(require,module,exports){const libPictView=require('pict-view')
|
|
2784
|
+
*/getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserBrowseTree;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":88}],72:[function(require,module,exports){const libPictView=require('pict-view');// Chunked rendering constants — for folders with very large file counts
|
|
2785
|
+
// we render rows in chunks via requestAnimationFrame so the main thread
|
|
2786
|
+
// stays responsive instead of freezing on one massive assignContent call.
|
|
2787
|
+
const _CHUNKED_RENDER_THRESHOLD=500;const _CHUNK_FIRST_SIZE=200;const _CHUNK_SUBSEQUENT_SIZE=400;const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ListDetail","DefaultRenderable":"ListDetail-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ListPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ListDetail-Container-Template","Template":/*html*/"\n<div class=\"pict-fb-detail\" id=\"Pict-FileBrowser-DetailList\">\n\t<div class=\"pict-fb-breadcrumb\" id=\"Pict-FileBrowser-Breadcrumb\"></div>\n\t<div class=\"pict-fb-detail-header\">\n\t\t<div class=\"pict-fb-detail-header-cell pict-fb-detail-col-name\" onclick=\"pict.views['{~D:Record.ViewHash~}'].sortBy('Name')\">Name</div>\n\t\t<div class=\"pict-fb-detail-header-cell pict-fb-detail-col-size\" onclick=\"pict.views['{~D:Record.ViewHash~}'].sortBy('Size')\">Size</div>\n\t\t<div class=\"pict-fb-detail-header-cell pict-fb-detail-col-modified\" onclick=\"pict.views['{~D:Record.ViewHash~}'].sortBy('Modified')\">Modified</div>\n\t</div>\n\t<div id=\"Pict-FileBrowser-DetailRows\"></div>\n</div>\n"},{"Hash":"FileBrowser-ListDetail-Row-Template","Template":/*html*/"\n<div class=\"pict-fb-detail-row{~D:Record.SelectedClass~}\" data-index=\"{~D:Record.Index~}\" onclick=\"{~D:Record.ClickHandler~}\" ondblclick=\"{~D:Record.DblClickHandler~}\">\n\t<span class=\"pict-fb-detail-icon\">{~D:Record.Icon~}</span>\n\t<span class=\"pict-fb-detail-name\">{~D:Record.Name~}</span>\n\t<span class=\"pict-fb-detail-size\">{~D:Record.SizeFormatted~}</span>\n\t<span class=\"pict-fb-detail-modified\">{~D:Record.ModifiedFormatted~}</span>\n</div>\n"},{"Hash":"FileBrowser-ListDetail-Empty-Template","Template":/*html*/"<div class=\"pict-fb-empty\">{~D:Record.Message~}</div>"},{"Hash":"FileBrowser-Breadcrumb-Segment-Template","Template":/*html*/"<span class=\"pict-fb-breadcrumb-segment\" onclick=\"{~D:Record.ClickHandler~}\">{~D:Record.Label~}</span>"},{"Hash":"FileBrowser-Breadcrumb-Separator-Template","Template":/*html*/"<span class=\"pict-fb-breadcrumb-separator\">/</span>"},{"Hash":"FileBrowser-Breadcrumb-Current-Template","Template":/*html*/"<span class=\"pict-fb-breadcrumb-current\">{~D:Record.Label~}</span>"}],"Renderables":[{"RenderableHash":"ListDetail-Container","TemplateHash":"FileBrowser-ListDetail-Container-Template","DestinationAddress":"#Pict-FileBrowser-ListPane","RenderMethod":"replace"}]};/**
|
|
2785
2788
|
* Listing view that shows files in a detailed table with columns for
|
|
2786
2789
|
* name, size, and modified date.
|
|
2787
2790
|
*
|
|
2788
2791
|
* Supports sorting by column header click and single-click selection
|
|
2789
2792
|
* with double-click to open folders.
|
|
2790
|
-
*/class PictViewFileBrowserListDetail extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);this._cachedFileList=[]
|
|
2793
|
+
*/class PictViewFileBrowserListDetail extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);this._cachedFileList=[];// Chunked render state
|
|
2794
|
+
this._activeRebuildFrame=null;this._activeRebuildToken=0;}/**
|
|
2795
|
+
* Cancel any in-flight chunked rebuild so it does not overlap with a
|
|
2796
|
+
* fresh navigation.
|
|
2797
|
+
*/_cancelActiveRebuild(){if(this._activeRebuildFrame!==null&&typeof cancelAnimationFrame==='function'){cancelAnimationFrame(this._activeRebuildFrame);this._activeRebuildFrame=null;}this._activeRebuildToken++;}/**
|
|
2791
2798
|
* After rendering the container shell, populate the rows.
|
|
2792
2799
|
*/onAfterRender(pRenderable){// Render the container with the view hash
|
|
2793
2800
|
let tmpContainerHTML=this.pict.parseTemplateByHash('FileBrowser-ListDetail-Container-Template',{ViewHash:this.Hash});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ListPane',tmpContainerHTML);this.rebuildList();this.rebuildBreadcrumb();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
|
|
2794
2801
|
* Rebuild the file list rows.
|
|
2795
|
-
|
|
2802
|
+
*
|
|
2803
|
+
* For folders with more than _CHUNKED_RENDER_THRESHOLD items, the render
|
|
2804
|
+
* is split into chunks scheduled via requestAnimationFrame so the main
|
|
2805
|
+
* thread stays responsive. Smaller folders use the synchronous fast path.
|
|
2806
|
+
*/rebuildList(){// Cancel any in-flight chunked rebuild from a previous folder
|
|
2807
|
+
this._cancelActiveRebuild();let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(!tmpListProvider){return;}let tmpFileList=tmpListProvider.getSortedFileList();this._cachedFileList=tmpFileList;let tmpSelectedFile=tmpListProvider.getSelectedFile();if(tmpFileList.length===0){let tmpEmptyHTML=this.pict.parseTemplateByHash('FileBrowser-ListDetail-Empty-Template',{Message:'This folder is empty'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-DetailRows',tmpEmptyHTML);return;}// SMALL FOLDER FAST PATH — keep the existing synchronous behavior
|
|
2808
|
+
// for normal-sized folders so there's no perceptible change.
|
|
2809
|
+
if(tmpFileList.length<=_CHUNKED_RENDER_THRESHOLD){let tmpHTML='';for(let i=0;i<tmpFileList.length;i++){tmpHTML+=this._buildRowHTML(tmpFileList[i],i,tmpSelectedFile);}this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-DetailRows',tmpHTML);return;}// LARGE FOLDER CHUNKED PATH — paint a loading row immediately, then
|
|
2810
|
+
// fill rows in chunks via requestAnimationFrame.
|
|
2811
|
+
let tmpLoadingHTML='<div class="retold-remote-filebrowser-loading-row">'+'Loading '+tmpFileList.length.toLocaleString()+' items\u2026'+'</div>';this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-DetailRows',tmpLoadingHTML);this._rebuildListChunked(tmpFileList,tmpSelectedFile);}/**
|
|
2812
|
+
* Build the HTML for a single row. Extracted so both the fast path
|
|
2813
|
+
* and the chunked path can share it.
|
|
2814
|
+
*/_buildRowHTML(pEntry,pIndex,pSelectedFile){let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];let tmpIsSelected=pSelectedFile&&pSelectedFile.Name===pEntry.Name&&pSelectedFile.Path===pEntry.Path;let tmpRecord={Index:pIndex,Name:pEntry.Name,Icon:tmpListProvider.getEntryIcon(pEntry),SizeFormatted:pEntry.Type==='folder'?'--':tmpListProvider.formatFileSize(pEntry.Size),ModifiedFormatted:tmpListProvider.formatDate(pEntry.Modified),SelectedClass:tmpIsSelected?' selected':'',ClickHandler:"pict.views['"+this.Hash+"'].selectEntry("+pIndex+")",DblClickHandler:"pict.views['"+this.Hash+"'].openEntry("+pIndex+")"};return this.pict.parseTemplateByHash('FileBrowser-ListDetail-Row-Template',tmpRecord);}/**
|
|
2815
|
+
* Render the file list into chunks via requestAnimationFrame.
|
|
2816
|
+
* After the first chunk replaces the loading row, each subsequent
|
|
2817
|
+
* chunk is appended to the rows container.
|
|
2818
|
+
*/_rebuildListChunked(pFileList,pSelectedFile){let tmpSelf=this;let tmpToken=++this._activeRebuildToken;let tmpTotal=pFileList.length;let tmpOffset=0;let _renderNextChunk2=function _renderNextChunk(){if(tmpToken!==tmpSelf._activeRebuildToken){return;}let tmpRowsContainer=document.getElementById('Pict-FileBrowser-DetailRows');if(!tmpRowsContainer){return;}let tmpChunkSize=tmpOffset===0?_CHUNK_FIRST_SIZE:_CHUNK_SUBSEQUENT_SIZE;let tmpEnd=Math.min(tmpOffset+tmpChunkSize,tmpTotal);let tmpChunkHTML='';for(let i=tmpOffset;i<tmpEnd;i++){tmpChunkHTML+=tmpSelf._buildRowHTML(pFileList[i],i,pSelectedFile);}if(tmpOffset===0){// Replace the loading row on the first chunk
|
|
2819
|
+
tmpRowsContainer.innerHTML=tmpChunkHTML;}else{// Append subsequent chunks
|
|
2820
|
+
tmpRowsContainer.insertAdjacentHTML('beforeend',tmpChunkHTML);}tmpOffset=tmpEnd;if(tmpOffset<tmpTotal){tmpSelf._activeRebuildFrame=requestAnimationFrame(_renderNextChunk2);}else{tmpSelf._activeRebuildFrame=null;}};if(typeof requestAnimationFrame==='function'){this._activeRebuildFrame=requestAnimationFrame(_renderNextChunk2);}else{// No rAF (shouldn't happen in a browser, but be safe) — fall back
|
|
2821
|
+
// to synchronous render
|
|
2822
|
+
_renderNextChunk2();while(tmpOffset<tmpTotal){_renderNextChunk2();}}}/**
|
|
2796
2823
|
* Rebuild the breadcrumb navigation bar.
|
|
2797
2824
|
*/rebuildBreadcrumb(){let tmpCurrentLocation=this.getCurrentLocation();let tmpHTML='';// Root segment — use SVG home icon if provider is available
|
|
2798
2825
|
let tmpIconProvider=this.pict.providers['Pict-FileBrowser-Icons'];let tmpHomeLabel=tmpIconProvider?tmpIconProvider.getIcon('home',16):'\uD83C\uDFE0';tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Segment-Template',{Label:tmpHomeLabel,ClickHandler:"pict.views['"+this.Hash+"'].navigateToPath('')"});if(tmpCurrentLocation){let tmpParts=tmpCurrentLocation.split('/');let tmpAccumulatedPath='';for(let i=0;i<tmpParts.length;i++){tmpAccumulatedPath=tmpAccumulatedPath?tmpAccumulatedPath+'/'+tmpParts[i]:tmpParts[i];tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Separator-Template',{});if(i===tmpParts.length-1){// Last segment — not clickable
|
|
@@ -3770,13 +3797,29 @@ this.pict.ContentAssignment.projectContent(pRenderable.RenderMethod,pRenderable.
|
|
|
3770
3797
|
*
|
|
3771
3798
|
* @param {Renderable} pRenderable - The renderable that was rendered.
|
|
3772
3799
|
*/onAfterRender(pRenderable){if(this.pict.LogNoisiness>3){this.log.trace("PictView [".concat(this.UUID,"]::[").concat(this.Hash,"] ").concat(this.options.ViewIdentifier," onAfterRender:"));}if(pRenderable&&pRenderable.RootRenderableViewHash===this.Hash){const tmpTransactionQueue=this.pict.TransactionTracking.clearTransactionQueue(pRenderable.TransactionHash)||[];for(const tmpEvent of tmpTransactionQueue){const tmpView=this.pict.views[tmpEvent.Data.ViewHash];if(!tmpView){this.log.error("PictView [".concat(this.UUID,"]::[").concat(this.Hash,"] ").concat(this.options.ViewIdentifier," onAfterRender: Could not find view for transaction hash ").concat(pRenderable.TransactionHash," and ViewHash ").concat(tmpEvent.Data.ViewHash,"."));continue;}tmpView.onAfterProject();// Execute the developer-overridable post-render behavior
|
|
3773
|
-
tmpView.onAfterRender(tmpEvent.Data.Renderable);}
|
|
3800
|
+
tmpView.onAfterRender(tmpEvent.Data.Renderable);}// Queue is drained and nested child renders have each cleaned up
|
|
3801
|
+
// their own transactions; remove this root render's entry from
|
|
3802
|
+
// the tracking map so it does not leak.
|
|
3803
|
+
this.pict.TransactionTracking.unregisterTransaction(pRenderable.TransactionHash);}return true;}/**
|
|
3774
3804
|
* Lifecycle hook that triggers after the view is rendered (async flow).
|
|
3775
3805
|
*
|
|
3776
3806
|
* @param {ErrorCallback} fCallback - The callback to call when the async operation is complete.
|
|
3777
3807
|
* @param {Renderable} pRenderable - The renderable that was rendered.
|
|
3778
|
-
*/onAfterRenderAsync(fCallback,pRenderable){
|
|
3779
|
-
|
|
3808
|
+
*/onAfterRenderAsync(fCallback,pRenderable){// NOTE: this.onAfterRender(pRenderable) will itself clear the
|
|
3809
|
+
// transaction queue and unregister the transaction if this view is
|
|
3810
|
+
// the root renderable - see onAfterRender above. So by the time the
|
|
3811
|
+
// loop below runs, the queue is already empty and there is nothing
|
|
3812
|
+
// to drain. Keeping the async queue walk here defensively in case
|
|
3813
|
+
// future subclasses override onAfterRender in ways that skip the
|
|
3814
|
+
// drain, but the common path is now "sync drain, async no-op".
|
|
3815
|
+
this.onAfterRender(pRenderable);const tmpAnticipate=this.fable.newAnticipate();const tmpIsRootRenderable=pRenderable&&pRenderable.RootRenderableViewHash===this.Hash;if(tmpIsRootRenderable){const queue=this.pict.TransactionTracking.clearTransactionQueue(pRenderable.TransactionHash)||[];for(const event of queue){/** @type {PictView} */const tmpView=this.pict.views[event.Data.ViewHash];if(!tmpView){this.log.error("PictView [".concat(this.UUID,"]::[").concat(this.Hash,"] ").concat(this.options.ViewIdentifier," onAfterRenderAsync: Could not find view for transaction hash ").concat(pRenderable.TransactionHash," and ViewHash ").concat(event.Data.ViewHash,"."));continue;}tmpAnticipate.anticipate(tmpView.onAfterProjectAsync.bind(tmpView));tmpAnticipate.anticipate(fNext=>{tmpView.onAfterRenderAsync(fNext,event.Data.Renderable);});// Execute the developer-overridable post-render behavior
|
|
3816
|
+
}}return tmpAnticipate.wait(pError=>{// Nested virtual-assignment children have now settled their own
|
|
3817
|
+
// onAfterRenderAsync chains (and unregistered their own
|
|
3818
|
+
// transactions along the way). Ensure this root render's entry
|
|
3819
|
+
// is also gone - unregisterTransaction is a no-op if the sync
|
|
3820
|
+
// onAfterRender above already removed it, so this is safe to
|
|
3821
|
+
// call unconditionally on the root path.
|
|
3822
|
+
if(tmpIsRootRenderable&&pRenderable&&pRenderable.TransactionHash){this.pict.TransactionTracking.unregisterTransaction(pRenderable.TransactionHash);}return fCallback(pError);});}/**
|
|
3780
3823
|
* Lifecycle hook that triggers after the view is projected into the DOM.
|
|
3781
3824
|
*
|
|
3782
3825
|
* @param {Renderable} pRenderable - The renderable that was projected.
|