11import { BaseApp } from "./baseApp.js" ;
22import { resolveIconClass } from "../utils.js" ;
33import { registry } from "../registry.js" ;
4- import { getImageUrl } from "../imageStore.js" ; // Import Image Loader
4+ import { getImageUrl } from "../imageStore.js" ;
55
66export class LinkApp extends BaseApp {
77 async render ( app ) {
88 const data = app . data || { } ;
99 const url = data . url || '#' ;
1010 const hideLabel = data . hideLabel === true || data . hideLabel === 'true' ;
11+ const displayMode = data . display || 'standard' ; // 'standard' | 'cover'
12+
13+ // Custom Styles (Only applied in Standard mode)
14+ let customStyle = '' ;
15+ if ( displayMode === 'standard' ) {
16+ const sizeVal = data . iconSize ? `height: ${ data . iconSize } ; font-size: ${ data . iconSize } ;` : '' ;
17+ customStyle = sizeVal ? `style="${ sizeVal } "` : '' ;
18+ }
1119
1220 let iconInput = data . icon || 'fa-globe' ;
1321 let isImage = false ;
@@ -25,7 +33,7 @@ export class LinkApp extends BaseApp {
2533 console . warn ( "[LinkApp] Failed to load image" , e ) ;
2634 }
2735 }
28- // 2. Check if it's a URL (heuristic: contains / or .)
36+ // 2. Check if it's a URL
2937 else if ( iconInput . includes ( '/' ) || iconInput . includes ( '.' ) ) {
3038 imgSrc = iconInput ;
3139 isImage = true ;
@@ -34,15 +42,17 @@ export class LinkApp extends BaseApp {
3442 // 3. Render HTML
3543 let iconHtml ;
3644 if ( isImage ) {
37- iconHtml = `<img src="${ imgSrc } " class="link-app-icon" alt="icon">` ;
45+ iconHtml = `<img src="${ imgSrc } " class="link-app-icon" alt="icon" ${ customStyle } >` ;
3846 } else {
39- // Fallback to FontAwesome
4047 const iconClass = resolveIconClass ( iconInput ) ;
41- iconHtml = `<i class="${ iconClass } "></i>` ;
48+ iconHtml = `<i class="${ iconClass } " ${ customStyle } ></i>` ;
4249 }
4350
51+ // Add specific class for cover mode
52+ const modeClass = displayMode === 'cover' ? 'mode-cover' : '' ;
53+
4454 return `
45- <a href="${ url } " target="_blank" class="app-content app-type-link">
55+ <a href="${ url } " target="_blank" class="app-content app-type-link ${ modeClass } ">
4656 ${ iconHtml }
4757 ${ ! hideLabel ? `<span>${ app . name } </span>` : '' }
4858 </a>` ;
@@ -54,8 +64,21 @@ registry.register('link', LinkApp, {
5464 defaultSize : { cols : 1 , rows : 1 } ,
5565 settings : [
5666 { name : 'url' , label : 'URL' , type : 'text' , placeholder : 'https://...' } ,
57- // Changed type to 'image-source' to enable Uploads
58- { name : 'icon' , label : 'Icon (Upload or FA Class)' , type : 'image-source' , placeholder : 'fa-fire OR https://...' } ,
67+ { name : 'icon' , label : 'Icon' , type : 'image-source' , placeholder : 'fa-fire OR https://...' } ,
68+
69+ // New: Display Mode Selection
70+ {
71+ name : 'display' ,
72+ label : 'Display Mode' ,
73+ type : 'select' ,
74+ defaultValue : 'standard' ,
75+ options : [
76+ { label : 'Standard (Icon + Text)' , value : 'standard' } ,
77+ { label : 'Cover (Full Card)' , value : 'cover' }
78+ ]
79+ } ,
80+
81+ { name : 'iconSize' , label : 'Icon Size (Standard Mode)' , type : 'text' , placeholder : 'e.g. 50px (ignored in Cover mode)' } ,
5982 { name : 'hideLabel' , label : 'Hide Text Label' , type : 'select' , options : [ { label :'No' , value :'false' } , { label :'Yes' , value :'true' } ] , defaultValue : 'false' }
6083 ] ,
6184 css : `
@@ -69,34 +92,70 @@ registry.register('link', LinkApp, {
6992 height: 100%;
7093 width: 100%;
7194 transition: color 0.2s;
95+ position: relative; /* For absolute children */
7296 }
7397 .app-card .app-type-link:hover { transform: scale(1.05); }
7498
75- /* FontAwesome Styling */
76- .app-type-link i { font-size: 2.5rem; margin-bottom: 10px; }
77-
78- /* Image/Upload Styling */
99+ /* --- STANDARD MODE --- */
100+ .app-type-link i { font-size: 2.5rem; margin-bottom: 10px; transition: all 0.2s; }
79101 .link-app-icon {
80102 height: 2.5rem;
81103 width: auto;
82104 margin-bottom: 10px;
83105 object-fit: contain;
84- pointer-events: none; /* Let clicks pass to link */
106+ pointer-events: none;
107+ transition: all 0.2s;
85108 }
109+ .app-type-link span { font-size: 1rem; text-align: center; z-index: 1; }
86110
87- .app-type-link span { font-size: 1rem; text-align: center; }
88-
89- /* "Only Child" = Large Icon Mode (No Text) */
90- .app-card .app-type-link i:only-child {
111+ /* Large Icon Mode (No Text) */
112+ .app-card .app-type-link:not(.mode-cover) i:only-child {
91113 font-size: 5rem;
92114 margin-bottom: 0;
93115 }
94- .app-card .app-type-link .link-app-icon:only-child {
116+ .app-card .app-type-link:not(.mode-cover) .link-app-icon:only-child {
95117 height: 5rem;
96- width: 80%; /* Prevent overflow if wide */
118+ width: 80%;
97119 margin-bottom: 0;
98120 }
99121
122+ /* --- COVER MODE --- */
123+ .app-type-link.mode-cover {
124+ padding: 0 !important;
125+ border-radius: var(--radius);
126+ overflow: hidden;
127+ }
128+
129+ /* Image Cover */
130+ .app-type-link.mode-cover .link-app-icon {
131+ position: absolute;
132+ top: 0; left: 0;
133+ width: 100%; height: 100%;
134+ margin: 0;
135+ object-fit: cover; /* This makes it fill the card */
136+ z-index: 0;
137+ }
138+
139+ /* Icon Cover (Big Centered) */
140+ .app-type-link.mode-cover i {
141+ font-size: 6rem; /* Huge size */
142+ margin: 0;
143+ opacity: 0.8;
144+ z-index: 0;
145+ }
146+
147+ /* Text Overlay */
148+ .app-type-link.mode-cover span {
149+ position: absolute;
150+ bottom: 0; left: 0; right: 0;
151+ background: rgba(0, 0, 0, 0.6);
152+ backdrop-filter: blur(4px);
153+ color: white;
154+ padding: 6px 4px;
155+ font-size: 0.85rem;
156+ text-shadow: 0 1px 2px black;
157+ }
158+
100159 .edit-mode .app-type-link {
101160 pointer-events: none;
102161 }
0 commit comments