directus-extension-csi-gallery-thumbnails 0.2.0 → 0.3.1

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.
Files changed (3) hide show
  1. package/README.md +16 -12
  2. package/dist/index.js +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,21 +44,25 @@ pnpm --filter directus-extension-csi-gallery-thumbnails build
44
44
 
45
45
  The build creates `dist/index.js`.
46
46
 
47
- ## Installation
47
+ ## Image enlargement (0.3.0)
48
+
49
+ Hover over a thumbnail for a 400px preview after a short delay. Keyboard focus
50
+ also previews an image. The preview stays within the viewport and closes when
51
+ scrolling, resizing, leaving the thumbnail, or pressing Escape. Click a thumbnail
52
+ (or press Enter/Space) to open a persistent, larger image dialog. Close with its
53
+ button, Escape, or the backdrop; focus returns to the thumbnail. Touch users can
54
+ tap to open the dialog. Neither action navigates to the product record.
48
55
 
49
- Like the other CSI extensions, it installs from the Directus Marketplace, which
50
- indexes the npm package:
56
+ Larger images load only on interaction: 800px for hover and 1600px for the dialog,
57
+ using uncropped transformations and the existing Directus session. Empty galleries,
58
+ non-image files, thumbnail spacing and wrapping retain their existing behavior.
51
59
 
52
- 1. Publish the package to npm using the GitHub workflow below.
53
- 2. Once the Marketplace lists it, install it with
54
- `POST /extensions/registry/install {"extension": "<marketplace id>"}`.
55
- 3. Set the field displays above.
60
+ ## Installation
56
61
 
57
- Self-hosted Directus currently loses registry-installed extensions on every
58
- container restart or instance replacement, so reinstall it with the others
59
- until the hosting team bakes the CSI extensions into the Directus image. While
60
- it is missing, the two fields show no thumbnails; switching their display back
61
- to Related Values restores the click-to-open list.
62
+ Publish the package using the workflow below, then upgrade the pinned version
63
+ in `directus/deploy/Dockerfile` and deploy the Beanstalk bundle following
64
+ [the deployment runbook](../../deploy/README.md). The production extension is
65
+ baked into the image; a Marketplace install does not replace that local package.
62
66
 
63
67
  ## Single-image activation
64
68
 
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{defineDisplay as e}from"@directus/extensions-sdk";import{defineComponent as t,reactive as n,computed as i,openBlock as a,createElementBlock as s,Fragment as r,renderList as l,unref as o,toDisplayString as u}from"vue";const c="directus_files_id",d=["id","type","title","modified_on"];function p(e){return("string"==typeof e?e.trim().replace(/^\.+|\.+$/g,""):"")||c}function f(e){return e.type?.startsWith("image/")??!1}function m(e,t){const n=new URL(`assets/${t.id}`,e);return n.searchParams.set("width","160"),n.searchParams.set("height","160"),n.searchParams.set("fit","contain"),n.searchParams.set("format","auto"),t.modified_on&&n.searchParams.set("v",t.modified_on),n.href}function h(e){const t=e.type?.split("/")[1]?.split(/[+.;-]/)[0];return(t||"file").slice(0,4).toUpperCase()}const y={key:0,class:"csi-gallery-thumbnails null"},g=["title"],b=["alt","src","onError"],v={key:1,class:"thumb file"};var x=t({__name:"display",props:{value:{},type:{},filePath:{}},setup(e){const t=e,c=function(e){const t=e.split("/"),n=t.indexOf("admin");return`${t.slice(0,n).join("/")}/`}(window.location.href),d=n(new Set),x=i(()=>function(e,t,n){const i="uuid"===n?[e]:Array.isArray(e)?e:[],a=p(t);return i.flatMap(e=>{const t="uuid"===n?e:function(e,t){return t.split(".").reduce((e,t)=>{if(e&&"object"==typeof e)return e[t]},e)}(e,a);if(!t||"object"!=typeof t)return[];const{id:i,type:s,title:r,modified_on:l}=t;return"string"==typeof i&&i?[{id:i,type:"string"==typeof s?s:null,title:"string"==typeof r?r:null,modified_on:"string"==typeof l?l:null}]:[]})}(t.value,t.filePath,t.type)),w=i(()=>`${x.value.length} ${1===x.value.length?"file":"files"}`);return(e,t)=>0===x.value.length?(a(),s("span",y,"--")):(a(),s("span",{key:1,class:"csi-gallery-thumbnails",title:w.value},[(a(!0),s(r,null,l(x.value,(e,t)=>(a(),s(r,{key:`${e.id}-${t}`},[o(f)(e)&&!d.has(e.id)?(a(),s("img",{key:0,alt:e.title??"",src:o(m)(o(c),e),class:"thumb",loading:"lazy",onError:t=>{return n=e.id,void d.add(n);var n}},null,40,b)):(a(),s("span",v,u(o(h)(e)),1))],64))),128))],8,g))}}),w=[],_=[];!function(e,t){if(e&&"undefined"!=typeof document){var n,i=!0===t.prepend?"prepend":"append",a=!0===t.singleTag,s="string"==typeof t.container?document.querySelector(t.container):document.getElementsByTagName("head")[0];if(a){var r=w.indexOf(s);-1===r&&(r=w.push(s)-1,_[r]={}),n=_[r]&&_[r][i]?_[r][i]:_[r][i]=l()}else n=l();65279===e.charCodeAt(0)&&(e=e.substring(1)),n.styleSheet?n.styleSheet.cssText+=e:n.appendChild(document.createTextNode(e))}function l(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),t.attributes)for(var n=Object.keys(t.attributes),a=0;a<n.length;a++)e.setAttribute(n[a],t.attributes[n[a]]);var r="prepend"===i?"afterbegin":"beforeend";return s.insertAdjacentElement(r,e),e}}('\n.csi-gallery-thumbnails[data-v-0d10bd92] {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px;\n max-inline-size: 100%;\n min-inline-size: 0;\n vertical-align: middle;\n}\n.thumb[data-v-0d10bd92] {\n flex: none;\n inline-size: var(--csi-gallery-thumbnail-size, 32px);\n block-size: var(--csi-gallery-thumbnail-size, 32px);\n border-radius: var(--theme--border-radius);\n object-fit: contain;\n background-color: var(--theme--background-normal);\n}\n.file[data-v-0d10bd92] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--theme--foreground-subdued);\n font-size: 9px;\n font-weight: 600;\n}\n.null[data-v-0d10bd92] {\n color: var(--theme--foreground-subdued);\n}\n\n/* Directus 12.3 table rows expose their native spacing as Vue CSS variables:\n 29/43/58px plus a 2px border. Match those values without depending on the\n generated variable name. Leave the native variables intact so switching\n spacing continues to work; only rows containing this display grow. */\n.table-row:has(.csi-gallery-thumbnails) {\n --csi-gallery-thumbnail-size: 32px;\n block-size: auto;\n min-block-size: calc(var(--csi-gallery-thumbnail-size) + 16px);\n}\n.table-row[style*=": 45px;"]:has(.csi-gallery-thumbnails) {\n --csi-gallery-thumbnail-size: 56px;\n}\n.table-row[style*=": 60px;"]:has(.csi-gallery-thumbnails) {\n --csi-gallery-thumbnail-size: 80px;\n}\n',{});var z=e({id:"csi-gallery-thumbnails",name:"Image Thumbnails",icon:"photo_library",description:"Show single images or every gallery image, sized to the table spacing.",component:((e,t)=>{const n=e.__vccOpts||e;for(const[e,i]of t)n[e]=i;return n})(x,[["__scopeId","data-v-0d10bd92"]]),types:["alias","uuid"],localTypes:["files","m2m","o2m","file"],fields:(e,t)=>function(e,t){if("uuid"===t)return[...d];const n=p(e);return d.map(e=>`${n}.${e}`)}(e?.filePath,t.type),options:e=>"uuid"===e.field?.type?[]:[{field:"filePath",name:"Path to the file",type:"string",meta:{interface:"input",width:"half",note:"From one gallery row to its file. Files field: directus_files_id. Asset Library: asset_library_id.file",options:{placeholder:c,font:"monospace"}},schema:{default_value:c}}]});export{z as default};
1
+ import{defineDisplay as e}from"@directus/extensions-sdk";import{defineComponent as n,reactive as t,computed as i,ref as a,watch as r,onBeforeUnmount as o,openBlock as l,createElementBlock as s,Fragment as c,renderList as u,unref as d,withKeys as p,withModifiers as f,createElementVNode as m,toDisplayString as g,createBlock as b,Teleport as h,normalizeStyle as v,createCommentVNode as y,nextTick as x}from"vue";const w="directus_files_id",k=["id","type","title","modified_on"];function z(e){return("string"==typeof e?e.trim().replace(/^\.+|\.+$/g,""):"")||w}function _(e){return e.type?.startsWith("image/")??!1}function j(e,n){return C(e,n,160,"contain")}function C(e,n,t,i="inside"){const a=new URL(`assets/${n.id}`,e);return a.searchParams.set("width",String(t)),a.searchParams.set("height",String(t)),a.searchParams.set("fit",i),a.searchParams.set("format","auto"),n.modified_on&&a.searchParams.set("v",n.modified_on),a.href}function E(e){const n=e.type?.split("/")[1]?.split(/[+.;-]/)[0];return(n||"file").slice(0,4).toUpperCase()}const P={key:0,class:"csi-gallery-thumbnails null"},T={key:1,class:"csi-gallery-thumbnails"},$=["aria-label","onPointerenter","onFocus","onClick","onKeydown"],M=["alt","src","onError"],S={key:1,class:"thumb file"},A=["src"],L={key:1},I=["onKeydown"],O=["src","alt"],F={key:1,role:"status"};var K=n({__name:"display",props:{value:{},type:{},filePath:{}},setup(e){const n=e,w=function(e){const n=e.split("/"),t=n.indexOf("admin");return`${n.slice(0,t).join("/")}/`}(window.location.href),k=t(new Set),K=i(()=>function(e,n,t){const i="uuid"===t?[e]:Array.isArray(e)?e:[],a=z(n);return i.flatMap(e=>{const n="uuid"===t?e:function(e,n){return n.split(".").reduce((e,n)=>{if(e&&"object"==typeof e)return e[n]},e)}(e,a);if(!n||"object"!=typeof n)return[];const{id:i,type:r,title:o,modified_on:l}=n;return"string"==typeof i&&i?[{id:i,type:"string"==typeof r?r:null,title:"string"==typeof o?o:null,modified_on:"string"==typeof l?l:null}]:[]})}(n.value,n.filePath,n.type)),B=a(null),D=a({}),N=a(!1),R=a(null),U=a(!1),W=a(null);let q;function H(){clearTimeout(q),B.value=null,window.removeEventListener("scroll",H,!0),window.removeEventListener("resize",H)}function V(e,n){if(H(),R.value||!e.isConnected)return;const t=function(e,n,t){const i=12,a=Math.max(1,Math.min(400,n-24,t-24)),r=e.right+i+a<=n-i?e.right+i:e.left-i-a;return{left:Math.max(i,Math.min(r,n-a-i)),top:Math.max(i,Math.min(e.top,t-a-i)),width:a,height:a}}(e.getBoundingClientRect(),window.innerWidth,window.innerHeight);D.value=Object.fromEntries(Object.entries(t).map(([e,n])=>[e,`${n}px`])),N.value=!1,B.value=n,window.addEventListener("scroll",H,!0),window.addEventListener("resize",H)}function G(){W.value?.close(),R.value=null}function J(e){e.target===W.value&&G()}return r(K,()=>{H(),G()}),o(()=>{H(),G()}),(e,n)=>(l(),s(c,null,[0===K.value.length?(l(),s("span",P,"--")):(l(),s("span",T,[(l(!0),s(c,null,u(K.value,(e,t)=>(l(),s(c,{key:`${e.id}-${t}`},[d(_)(e)&&!k.has(e.id)?(l(),s("button",{key:0,type:"button",class:"image-button","aria-label":`Enlarge ${e.title||`image ${t+1}`}`,"aria-haspopup":"dialog",onPointerenter:n=>function(e,n){if("touch"===e.pointerType)return;H();const t=e.currentTarget;q=setTimeout(()=>V(t,n),150)}(n,e),onPointerleave:H,onFocus:n=>function(e,n){V(e.currentTarget,n)}(n,e),onBlur:H,onClick:f(n=>async function(e){H(),U.value=!1,R.value=e,await x(),W.value?.showModal()}(e),["stop"]),onDblclick:n[0]||(n[0]=f(()=>{},["stop"])),onKeydown:[n[1]||(n[1]=p(f(()=>{},["stop"]),["enter"])),n[2]||(n[2]=p(f(()=>{},["stop"]),["space"])),p(f(H,["stop"]),["esc"])]},[m("img",{alt:e.title??"",src:d(j)(d(w),e),class:"thumb",loading:"lazy",onError:n=>{return t=e.id,k.add(t),void(B.value?.id===t&&H());var t}},null,40,M)],40,$)):(l(),s("span",S,g(d(E)(e)),1))],64))),128))])),(l(),b(h,{to:"body"},[B.value?(l(),s("div",{key:0,class:"csi-image-preview",style:v(D.value),"aria-hidden":"true"},[N.value?(l(),s("span",L,"Image preview unavailable")):(l(),s("img",{key:B.value.id,src:d(C)(d(w),B.value,800),alt:"",onError:n[3]||(n[3]=e=>N.value=!0)},null,40,A))],4)):y("v-if",!0),m("dialog",{ref_key:"dialog",ref:W,class:"csi-image-dialog","aria-label":"Image preview",onClick:f(J,["stop"]),onKeydown:p(f(G,["stop","prevent"]),["esc"]),onClose:n[5]||(n[5]=e=>R.value=null),onCancel:n[6]||(n[6]=e=>R.value=null)},[R.value?(l(),s(c,{key:0},[m("header",null,[m("span",null,g(R.value.title||"Image preview"),1),m("button",{type:"button",autofocus:"","aria-label":"Close image preview",onClick:G}," × ")]),U.value?(l(),s("p",F,"Image preview unavailable")):(l(),s("img",{key:R.value.id,src:d(C)(d(w),R.value,1600),alt:R.value.title??"",onError:n[4]||(n[4]=e=>U.value=!0)},null,40,O))],64)):y("v-if",!0)],40,I)]))],64))}}),B=[],D=[];!function(e,n){if(e&&"undefined"!=typeof document){var t,i=!0===n.prepend?"prepend":"append",a=!0===n.singleTag,r="string"==typeof n.container?document.querySelector(n.container):document.getElementsByTagName("head")[0];if(a){var o=B.indexOf(r);-1===o&&(o=B.push(r)-1,D[o]={}),t=D[o]&&D[o][i]?D[o][i]:D[o][i]=l()}else t=l();65279===e.charCodeAt(0)&&(e=e.substring(1)),t.styleSheet?t.styleSheet.cssText+=e:t.appendChild(document.createTextNode(e))}function l(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),n.attributes)for(var t=Object.keys(n.attributes),a=0;a<t.length;a++)e.setAttribute(t[a],n.attributes[t[a]]);var o="prepend"===i?"afterbegin":"beforeend";return r.insertAdjacentElement(o,e),e}}('\n.csi-gallery-thumbnails[data-v-71b7e5c1] {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px;\n max-inline-size: 100%;\n min-inline-size: 0;\n vertical-align: middle;\n}\n.image-button[data-v-71b7e5c1] {\n display: inline-flex;\n flex: none;\n padding: 0;\n border: 0;\n border-radius: var(--theme--border-radius);\n background: transparent;\n cursor: zoom-in;\n}\n.image-button[data-v-71b7e5c1]:focus-visible {\n outline: 2px solid var(--theme--primary, #6644ff);\n outline-offset: 2px;\n}\n.thumb[data-v-71b7e5c1] {\n flex: none;\n inline-size: var(--csi-gallery-thumbnail-size, 32px);\n block-size: var(--csi-gallery-thumbnail-size, 32px);\n border-radius: var(--theme--border-radius);\n object-fit: contain;\n background-color: var(--theme--background-normal);\n}\n.file[data-v-71b7e5c1] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--theme--foreground-subdued);\n font-size: 9px;\n font-weight: 600;\n}\n.null[data-v-71b7e5c1] {\n color: var(--theme--foreground-subdued);\n}\n\n/* Directus 12.3 table rows expose their native spacing as Vue CSS variables:\n 29/43/58px plus a 2px border. Match those values without depending on the\n generated variable name. Leave the native variables intact so switching\n spacing continues to work; only rows containing this display grow. */\n.table-row:has(.csi-gallery-thumbnails) {\n --csi-gallery-thumbnail-size: 32px;\n block-size: auto;\n min-block-size: calc(var(--csi-gallery-thumbnail-size) + 16px);\n}\n.table-row[style*=": 45px;"]:has(.csi-gallery-thumbnails) {\n --csi-gallery-thumbnail-size: 56px;\n}\n.table-row[style*=": 60px;"]:has(.csi-gallery-thumbnails) {\n --csi-gallery-thumbnail-size: 80px;\n}\n.csi-image-preview[data-v-71b7e5c1] {\n position: fixed;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 8px;\n box-sizing: border-box;\n border: 1px solid var(--theme--border-color, #dde4e8);\n border-radius: var(--theme--border-radius, 6px);\n background: var(--theme--background, #fff);\n color: var(--theme--foreground, #26333f);\n box-shadow: 0 8px 32px #0003;\n pointer-events: none;\n}\n.csi-image-preview img[data-v-71b7e5c1] {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n.csi-image-dialog[data-v-71b7e5c1] {\n width: min(960px, calc(100vw - 32px));\n max-height: calc(100dvh - 32px);\n padding: 16px;\n box-sizing: border-box;\n border: 1px solid var(--theme--border-color, #dde4e8);\n border-radius: var(--theme--border-radius, 6px);\n background: var(--theme--background, #fff);\n color: var(--theme--foreground, #26333f);\n}\n.csi-image-dialog[data-v-71b7e5c1]::backdrop {\n background: #0009;\n}\n.csi-image-dialog header[data-v-71b7e5c1] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 12px;\n}\n.csi-image-dialog header span[data-v-71b7e5c1] {\n overflow-wrap: anywhere;\n}\n.csi-image-dialog button[data-v-71b7e5c1] {\n flex: none;\n width: 36px;\n height: 36px;\n border: 0;\n border-radius: 6px;\n font: 28px/1 sans-serif;\n cursor: pointer;\n color: inherit;\n background: var(--theme--background-normal, #f1f4f6);\n}\n.csi-image-dialog > img[data-v-71b7e5c1] {\n display: block;\n width: 100%;\n height: min(700px, 70dvh);\n object-fit: contain;\n}\n',{});var N=e({id:"csi-gallery-thumbnails",name:"Image Thumbnails",icon:"photo_library",description:"Show every image with hover previews and click-to-enlarge.",component:((e,n)=>{const t=e.__vccOpts||e;for(const[e,i]of n)t[e]=i;return t})(K,[["__scopeId","data-v-71b7e5c1"]]),types:["alias","uuid"],localTypes:["files","m2m","o2m","file"],fields:(e,n)=>function(e,n){if("uuid"===n)return[...k];const t=z(e);return k.map(e=>`${t}.${e}`)}(e?.filePath,n.type),options:e=>"uuid"===e.field?.type?[]:[{field:"filePath",name:"Path to the file",type:"string",meta:{interface:"input",width:"half",note:"From one gallery row to its file. Files field: directus_files_id. Asset Library: asset_library_id.file",options:{placeholder:w,font:"monospace"}},schema:{default_value:w}}]});export{N as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus-extension-csi-gallery-thumbnails",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Directus display for single images and complete galleries, sized to table spacing.",
5
5
  "keywords": [
6
6
  "directus",