import {Component_A} from './basic/as-component.js';
import {imgDefaultPng} from './style/img/images.js';
import {FP_Image_A} from './fp-ext/fp-img-ext.js';
import {ErrorCode} from '../exception/exceptionDesc.js';
/**
* @typedef TComponents.ImageProps
* @prop {object} [options] Additional options for the image component.
* Items in this object include:
* - **responsive** (boolean, default: false): Whether the image should be responsive.
* @prop {Function} [onCreated] Function to be called when the component is created.
* @prop {Function} [onMounted] Function to be called when the component is mounted.
* @prop {string} [imgSrc] Path to the image file.
* @prop {object} [imgStyle] Visual style configuration for the image.
* This object controls how the image is rendered:
* - **objectFit** (string, default: 'contain'): How the image should fit inside its container.
* - **opacity** (number, default: 1.0): Opacity of the image, from 0 to 1.
* @prop {Function} [onClick] Function to be called when the image is clicked.
* @prop {string} [position] CSS position property.
* @prop {number} [width] Width of the component.
* @prop {number} [height] Height of the component.
* @prop {number} [top] Top position of the component.
* @prop {number} [left] Left position of the component.
* @prop {number} [zIndex] Z-index of the component.
* @prop {number} [rotation] Rotation angle of the component.
* @prop {number} [borderRadius] Border radius of the component.
* @prop {string} [defaultState] Default state of the component.
* @memberof TComponents
*/
/**
* @ignore
*/
const logModule = 'as-image';
/**
* Image component that can display an image with specific styles and handle events.
* This class focuses on the specific properties of the Image component.
* Since it inherits from Accessor_A, all basic properties (e.g., height, width) are available but documented in the Accessor_A part.
* @class TComponents.Image
* @extends TComponents.Component_A
* @memberof TComponents
* @param {HTMLElement} parent - HTML element that is going to be the parent of the component
* @param {TComponents.ImageProps} props - Properties to initialize the component
* @example
* const imgComponent = new TComponents.Image(document.body, {
* position: 'absolute',
* zIndex: 1000,
* width: 200,
* height: 150,
* imgSrc: 'https://xxx.png',
* imgStyle: { objectFit: 'contain' }
* });
*
* imgComponent.render();
*/
export class Image extends Component_A {
constructor(parent, props = {}) {
super(parent, props);
/**
* @instance
* @private
*/
this._img = new FP_Image_A();
}
/**
* @description Returns the default values of class properties (excluding parent properties).
* @member TComponents.Image#defaultProps
* @method
* @returns {TComponents.ImageProps}
*/
defaultProps() {
return {
options: {
responsive: false,
},
tips: '',
// life cycle
onCreated: '',
onMounted: '',
onDispose: '',
// imgObj: {label: imgDefaultPng, value: 'default'},
imgSrc: imgDefaultPng,
imgStyle: {objectFit: 'contain', opacity: 1.0},
// imgFillStyle: 'fill',
onClick: '',
// ⭐ W/H/X/Y/B/R/Z: Component required attributes.
position: 'static',
width: 146,
height: 106,
top: 0,
left: 0,
zIndex: 0,
rotation: 0,
borderRadius: 4,
defaultState: 'show_enable',
};
}
/**
* @return {string}
*/
get tips() {
return this._props.tips;
}
/**
* @description Set the button disabled state and review the tips.
* @member {string} TComponents.Image#tips
* @instance
* @param {string} t
* @example
* const image = new TComponents.Image(document.body, {
* position: 'absolute',
* left: 100,
* top: 100,
* zIndex: 1000,
* imgSrc: 'https://xxx.png'
* });
*
* // Render the image
* image.render();
*
* image.tips = 'New tips for the image';
*
* // Disable image to display tips
* image.enabled = false;
*/
set tips(t) {
this.setProps({tips: t});
}
/**
* @description Generates the HTML markup for the component.
* @member TComponents.Image#markup
* @method
* @returns {string} The HTML markup string
*/
markup() {
return /*html*/ `<div class="tc-img"></div>`;
}
/**
* @description Handles the rendering process of the component, including setting up event listeners and applying image properties.
* @member TComponents.Image#onRender
* @method
* @returns {void}
*/
onRender() {
try {
this.removeAllEventListeners();
this._img.src = this._evalImgSrc();
this._img.fit = this._props.imgStyle.objectFit;
this._img.opacity = this._props.imgStyle.opacity;
this._img.borderRadius = this._props.borderRadius;
if (this._props.onClick) {
const fn = Component_A.genFuncTemplate(this._props.onClick, this);
if (fn) this._img.onclick = fn;
}
const imgContainer = this.find('.tc-img');
if (imgContainer) this._img.attachToElement(imgContainer);
this._addTips();
} catch (e) {
// Runtime errors: write specific content to the log, throw error code
Logger.e(
logModule,
ErrorCode.FailedToRunOnRender,
`Error happens on onRender of button component ${this.compId}.`,
e,
);
throw new Error(ErrorCode.FailedToRunOnRender, {cause: e});
}
}
/**
* @description Evaluates and retrieves the source of the image.
* If the `imgSrc` property references a global variable, it returns the value of that variable.
* Otherwise, it returns the original `imgSrc` property value.
* @member TComponents.Image~_evalImgSrc
* @method
* @private
* @throws {Error} Throws an error if the source image file cannot be found.
* @returns {string} The evaluated image source.
*/
_evalImgSrc() {
try {
const imgSrc = window[this._props.imgSrc];
if (typeof imgSrc == 'string') return imgSrc;
else {
Logger.w(logModule, ErrorCode.FailedToFindSourceImage, `Failed to find source image file.`);
return this._props.imgSrc;
}
} catch (e) {
Logger.e(logModule, ErrorCode.FailedToFindSourceImage, `Failed to find source image file.`);
throw ErrorCode.FailedToFindSourceImage;
}
}
/**
* @returns {string}
*/
get imgSrc() {
return this._props.imgSrc;
}
/**
* @description Sets the source of the image.
* @member {string} TComponents.Image#imgSrc
* @instance
* @param {string} s - The source URL of the image
* @example
* const imgComponent = new TComponents.Image(document.body, {
* position: 'absolute',
* zIndex: 1000,
* width: 200,
* height: 150,
* imgSrc: '',
* imgStyle: { objectFit: 'contain' }
* });
*
* imgComponent.render();
*
* // Set a new image source
* imgComponent.imgSrc = 'https://xxx.png';
*/
set imgSrc(s) {
this.setProps({imgSrc: s});
}
/**
* @returns {object}
*/
get imgStyle() {
return this._props.imgStyle;
}
/**
* @description Sets the style of the image.
* @member {object} TComponents.Image#imgStyle
* @instance
* @param {object} s - The style object for the image
* @example
* const imgComponent = new TComponents.Image(document.body, {
* position: 'absolute',
* zIndex: 1000,
* width: 200,
* height: 150,
* imgSrc: '',
* imgStyle: { objectFit: 'contain' }
* });
*
* imgComponent.render();
*
* // Set a new image style
* imgComponent.imgStyle = { objectFit: 'cover'};
*/
set imgStyle(s) {
this.setProps({imgStyle: s});
}
/**
* @returns {Function|undefined}
*/
get onClick() {
try {
var fn = Component_A.genFuncTemplateWithPopup(this._props.onClick, this);
} catch (e) {
return undefined;
}
if (typeof fn == 'function') return fn;
else return undefined;
}
/**
* @description Sets the `onClick` event handler.
* The handler can either be a string representing a function to be executed or a function itself.
* 1. If you are using an arrow function, like `()=>{}`,
* the `this` property of the scope may not refer to the image object.
* 2. If you are using string assignment to define code execution,
* the string should contain `only the body of the code (executable statements)`,
* not a complete function declaration. Therefore, including function keywords like function or async function is incorrect.
* - Correct (Statements Only): `xx.onClick = "console.log('Action done.');"`
* - Incorrect (Function Declaration): `xx.onClick = "function() { console.log('Action done.'); }"`
* @member {Function} TComponents.Image#onClick
* @instance
* @param {Function} t - The new click handler function.
* @example
* const imgComponent = new TComponents.Image(document.body, {
* position: 'absolute',
* zIndex: 1000,
* width: 200,
* height: 150,
* imgSrc: '',
* imgStyle: { objectFit: 'contain' }
* });
*
* imgComponent.render();
*
* // Example 1: Using a string as the handler:
* imgComponent.onClick = "console.log('Image clicked!', this.imgSrc);";
* @example
* // Example 2: Using a arrow function as the handler:
* // Note that the `this` context will not refer to the imgComponent object
* imgComponent.onClick = () => { console.log('Image clicked!', imgComponent.imgSrc); };
* @example
* // Example 3: Using a common function as the handler:
* imgComponent.onClick = async function() {
* console.log('Image clicked!', this.imgSrc);
* };
*/
set onClick(t) {
this.setProps({onClick: t});
}
}
/**
* @description Add css properties to the component
* @alias loadCssClassFromString
* @member TComponents.Image.loadCssClassFromString
* @method
* @static
* @param {string} css - The css string to be loaded into style tag
* @returns {void}
* @example
* TComponents.Image.loadCssClassFromString(`
* .tc-img {
* height: inherit;
* width: 100%;
* min-width: 0px;
* }`);
*/
Image.loadCssClassFromString(`
.tc-img {
height: inherit;
width: 100%;
min-width: 0px;
min-height: 0px;
padding: 0px;
margin: 0px;
display:grid;
}
.tc-img:not(.float) {
min-width: 0px;
min-height: 0px;
}
.tc-img .fp-components-img-disabled,
.tc-img .fp-components-img {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
min-width: 0px;
min-height: 0px;
overflow: hidden;
}
.tc-img .fp-components-img-disabled{
cursor: not-allowed !important;
}
.tc-img .fp-components-img:hover{
opacity:0.7;
}
`);