BIV v1.1.2

This commit is contained in:
_Lighty_ 2020-03-12 16:18:41 +01:00
parent 4eb22c0c7d
commit 6b8dbc0529
2 changed files with 153 additions and 186 deletions

View File

@ -1,4 +1,4 @@
//META{"name":"BetterImageViewer","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/BetterImageViewer/BetterImageViewer.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterImageViewer"}*// //META{"name":"BetterImageViewer","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/BetterImageViewer/BetterImageViewer.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterImageViewer","authorId":"239513071272329217","invite":"NYvWdN5","donate":"https://paypal.me/lighty13"}*//
/*@cc_on /*@cc_on
@if (@_jscript) @if (@_jscript)
@ -37,21 +37,16 @@ var BetterImageViewer = (() => {
twitter_username: '' twitter_username: ''
} }
], ],
version: '1.1.1', version: '1.1.2',
description: 'Adds ability to go between images in the current channel with arrow keys, or on screen buttons, and has click to zoom. Also provides info about the image, who posted it and when.', description: 'Adds ability to go between images in the current channel with arrow keys, or on screen buttons, and has click to zoom. Also provides info about the image, who posted it and when.',
github: 'https://github.com/1Lighty', github: 'https://github.com/1Lighty',
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/BetterImageViewer/BetterImageViewer.plugin.js' github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/BetterImageViewer/BetterImageViewer.plugin.js'
}, },
changelog: [ changelog: [
{ {
title: 'IMAGE ZOOM IS HERE!', title: 'Image zoom changes',
type: 'added', type: 'added',
items: ['*SOME* people kept asking, so here you go.\nAdded image zoom, simply activate by click and holding on the image.', 'Use scroll wheel to zoom in and out, hold shift while scrolling to change lens size.', "If you prefer using a different plugin to handle zooming for you, that's fine too, there is a toggle to disable it in settings.", 'The click and hold can of course be easily changed in the settings to your prefered method of zooming.\n![zoommode](https://i.imgur.com/A8HjQb9.png)', 'There are other options too, for those that may want it!\n![moreoptions](https://i.imgur.com/JGNe7Re.png)'] items: ['Improved performance when smoothing is disabled']
},
{
title: 'post v1.1.0 update',
type: 'fixed',
items: ['Fixed breaking videos, lmao']
} }
], ],
defaultConfig: [ defaultConfig: [
@ -199,7 +194,16 @@ var BetterImageViewer = (() => {
let NoImageZoom = false; let NoImageZoom = false;
let overlayDOMNode; let overlayDOMNode;
const { ARROW_LEFT, ARROW_RIGHT } = DiscordConstants.KeyboardKeys; const { ARROW_LEFT, ARROW_RIGHT } = (() => {
try {
const keys = DiscordConstants.KeyboardKeys;
if (!keys) throw 'KeyboardKeys is undefined';
return keys;
} catch (e) {
Logger.stacktrace('Failed to get KeyboardKeys', e);
return { ARROW_LEFT: 37, ARROW_RIGHT: 39 };
}
})();
const Clickable = WebpackModules.getByDisplayName('Clickable'); const Clickable = WebpackModules.getByDisplayName('Clickable');
const ImageUtils = Object.assign({}, WebpackModules.getByProps('getImageSrc'), WebpackModules.getByProps('getRatio')); const ImageUtils = Object.assign({}, WebpackModules.getByProps('getImageSrc'), WebpackModules.getByProps('getRatio'));
@ -245,7 +249,14 @@ var BetterImageViewer = (() => {
} }
const ReactSpring = WebpackModules.getByProps('useTransition'); const ReactSpring = WebpackModules.getByProps('useTransition');
const Easing = WebpackModules.getByProps('Easing').Easing; const Easing = (() => {
try {
return WebpackModules.getByProps('Easing').Easing;
} catch (e) {
Logger.stacktrace('Failed to get easing', e);
return null;
}
})();
class Image extends (() => { class Image extends (() => {
const Image = WebpackModules.getByDisplayName('Image'); const Image = WebpackModules.getByDisplayName('Image');
@ -381,6 +392,42 @@ var BetterImageViewer = (() => {
this.setState({ raw: fullSource }); this.setState({ raw: fullSource });
this.__BIV_updating = false; this.__BIV_updating = false;
} }
renderLens(ea, props) {
return React.createElement(
ReactSpring.animated.div,
{
style: {
width: props.panelWH,
height: props.panelWH,
left: props.panelX,
top: props.panelY,
opacity: ea.opacity
},
className: XenoLib.joinClassNames('BIV-zoom-lens', { 'BIV-zoom-lens-round': this.props.settings.round })
},
React.createElement(
this.props.settings.smoothing ? ReactSpring.animated.div : 'div',
{
style: {
position: 'absolute',
left: props.imgLeft,
top: props.imgTop
}
},
React.createElement(this.props.__BIV_animated ? (this.props.settings.smoothing ? ReactSpring.animated.video : 'video') : this.props.settings.smoothing ? ReactSpring.animated.img : 'img', {
src: this.props.__BIV_animated ? this.props.__BIV_src : this.state.raw,
width: props.imgWidth,
height: props.imgHeight,
style: this.props.settings.interp
? undefined
: {
imageRendering: 'pixelated'
},
...(this.props.__BIV_animated ? { autoPlay: true, muted: true, loop: true } : {})
})
)
);
}
render() { render() {
const ret = super.render(); const ret = super.render();
ret.props.onMouseDown = this.handleMouseDown; ret.props.onMouseDown = this.handleMouseDown;
@ -407,51 +454,18 @@ var BetterImageViewer = (() => {
}, },
className: 'BIV-zoom-backdrop' className: 'BIV-zoom-backdrop'
}), }),
React.createElement( this.props.settings.smoothing
ReactSpring.Spring, ? React.createElement(
{ ReactSpring.Spring,
native: true,
from: { panelX: this.state.panelX[1], panelY: this.state.panelY[1], panelWH: this.state.panelWH[1], offsetX: this.state.offsetX[1], offsetY: this.state.offsetY[1], zoom: this.state.zoom[1], opacity: 0 },
to: { panelX: this.state.panelX[0], panelY: this.state.panelY[0], panelWH: this.state.panelWH[0], offsetX: this.state.offsetX[0], offsetY: this.state.offsetY[0], zoom: this.state.zoom[0], opacity: 1 },
immediate: !this.props.settings.smoothing,
config: { mass: 1, tension: 1500, friction: 75, easing: Easing.linear, clamp: false }
},
e =>
React.createElement(
ReactSpring.animated.div,
{ {
style: { native: true,
width: e.panelWH, from: { panelX: this.state.panelX[1], panelY: this.state.panelY[1], panelWH: this.state.panelWH[1], offsetX: this.state.offsetX[1], offsetY: this.state.offsetY[1], zoom: this.state.zoom[1], opacity: 0 },
height: e.panelWH, to: { panelX: this.state.panelX[0], panelY: this.state.panelY[0], panelWH: this.state.panelWH[0], offsetX: this.state.offsetX[0], offsetY: this.state.offsetY[0], zoom: this.state.zoom[0], opacity: 1 },
left: e.panelX, config: { mass: 1, tension: 1750, friction: 75, easing: Easing ? Easing.linear : e => e, clamp: false }
top: e.panelY,
opacity: ea.opacity
},
className: XenoLib.joinClassNames('BIV-zoom-lens', { 'BIV-zoom-lens-round': this.props.settings.round })
}, },
React.createElement( e => this.renderLens(ea, { ...e, imgLeft: ReactSpring.to([e.zoom, e.offsetX, e.panelWH], (z, x, wh) => -x * z + wh / 2), imgTop: ReactSpring.to([e.zoom, e.offsetY, e.panelWH], (z, y, wh) => -y * z + wh / 2), imgWidth: e.zoom.to(e => e * this.props.width), imgHeight: e.zoom.to(e => e * this.props.height) })
ReactSpring.animated.div,
{
style: {
position: 'absolute',
left: ReactSpring.to([e.zoom, e.offsetX, e.panelWH], (z, x, wh) => -x * z + wh / 2),
top: ReactSpring.to([e.zoom, e.offsetY, e.panelWH], (z, y, wh) => -y * z + wh / 2)
}
},
React.createElement(this.props.__BIV_animated ? ReactSpring.animated.video : ReactSpring.animated.img, {
src: this.props.__BIV_animated ? this.props.__BIV_src : this.state.raw,
width: e.zoom.to(e => e * this.props.width),
height: e.zoom.to(e => e * this.props.height),
style: this.props.settings.interp
? undefined
: {
imageRendering: 'pixelated'
},
...(this.props.__BIV_animated ? { autoPlay: true, muted: true, loop: true } : {})
})
)
) )
) : this.renderLens(ea, { panelWH: this.state.panelWH[0], panelX: this.state.panelX[0], panelY: this.state.panelY[0], imgLeft: -this.state.offsetX[0] * this.state.zoom[0] + this.state.panelWH[0] / 2, imgTop: -this.state.offsetY[0] * this.state.zoom[0] + this.state.panelWH[0] / 2, imgWidth: this.state.zoom[0] * this.props.width, imgHeight: this.state.zoom[0] * this.props.height })
] ]
), ),
overlayDOMNode overlayDOMNode
@ -1188,6 +1202,9 @@ var BetterImageViewer = (() => {
} catch (e) {} } catch (e) {}
} }
}; };
try {
ModalStack.popWithKey(`${this.name}_DEP_MODAL`);
} catch (e) {}
} }
onStart() { onStart() {
if (!overlayDOMNode) { if (!overlayDOMNode) {
@ -1444,7 +1461,9 @@ var BetterImageViewer = (() => {
} }
async patchMessageAccessories(promiseState) { async patchMessageAccessories(promiseState) {
const MessageAccessories = await ReactComponents.getComponentByName('MessageAccessories', `.${XenoLib.getSingleClass('embedWrapper container')}`); const selector = `.${XenoLib.getSingleClass('embedWrapper container')}`;
const MessageAccessories = await ReactComponents.getComponentByName('MessageAccessories', selector);
if (!MessageAccessories.selector) MessageAccessories.selector = selector;
if (promiseState.cancelled) return; if (promiseState.cancelled) return;
Patcher.before(MessageAccessories.component.prototype, 'render', _this => { Patcher.before(MessageAccessories.component.prototype, 'render', _this => {
_this.__BIV_data = { _this.__BIV_data = {
@ -1681,24 +1700,25 @@ var BetterImageViewer = (() => {
/* Finalize */ /* Finalize */
/* this new lib loader is lit */
let ZeresPluginLibraryOutdated = false; let ZeresPluginLibraryOutdated = false;
let XenoLibOutdated = false; let XenoLibOutdated = false;
try { try {
if (global.BdApi && typeof BdApi.getPlugin === 'function' /* you never know with those retarded client mods */) { if (global.BdApi && 'function' == typeof BdApi.getPlugin) {
const versionChecker = (a, b) => ((a = a.split('.').map(a => parseInt(a))), (b = b.split('.').map(a => parseInt(a))), !!(b[0] > a[0])) || !!(b[0] == a[0] && b[1] > a[1]) || !!(b[0] == a[0] && b[1] == a[1] && b[2] > a[2]); const i = (i, n) => ((i = i.split('.').map(i => parseInt(i))), (n = n.split('.').map(i => parseInt(i))), !!(n[0] > i[0]) || !!(n[0] == i[0] && n[1] > i[1]) || !!(n[0] == i[0] && n[1] == i[1] && n[2] > i[2])),
const isOutOfDate = (lib, minVersion) => lib && lib._config && lib._config.info && lib._config.info.version && versionChecker(lib._config.info.version, minVersion); n = (n, e) => n && n._config && n._config.info && n._config.info.version && i(n._config.info.version, e),
const iZeresPluginLibrary = BdApi.getPlugin('ZeresPluginLibrary'); e = BdApi.getPlugin('ZeresPluginLibrary'),
const iXenoLib = BdApi.getPlugin('XenoLib'); o = BdApi.getPlugin('XenoLib');
if (isOutOfDate(iZeresPluginLibrary, '1.2.10')) ZeresPluginLibraryOutdated = true; n(e, '1.2.11') && (ZeresPluginLibraryOutdated = !0), n(o, '1.3.14') && (XenoLibOutdated = !0);
if (isOutOfDate(iXenoLib, '1.3.13')) XenoLibOutdated = true;
} }
} catch (e) { } catch (i) {
console.error('Error checking if libraries are out of date', e); console.error('Error checking if libraries are out of date', i);
} }
return !global.ZeresPluginLibrary || !global.XenoLib || ZeresPluginLibraryOutdated || XenoLibOutdated return !global.ZeresPluginLibrary || !global.XenoLib || ZeresPluginLibraryOutdated || XenoLibOutdated
? class { ? class {
constructor() {
this._XL_PLUGIN = true;
}
getName() { getName() {
return this.name.replace(/\s+/g, ''); return this.name.replace(/\s+/g, '');
} }
@ -1713,135 +1733,79 @@ var BetterImageViewer = (() => {
} }
stop() {} stop() {}
load() { load() {
const XenoLibMissing = !global.XenoLib; const a = BdApi.findModuleByProps('isModalOpen');
const zlibMissing = !global.ZeresPluginLibrary; if (a && a.isModalOpen(`${this.name}_DEP_MODAL`)) return;
const bothLibsMissing = XenoLibMissing && zlibMissing; const b = !global.XenoLib,
const bothLibsShit = bothLibsMissing || ((XenoLibMissing || zlibMissing) && (XenoLibOutdated || ZeresPluginLibraryOutdated)) || XenoLibOutdated || ZeresPluginLibraryOutdated; c = !global.ZeresPluginLibrary,
const header = (() => { d = (b && c) || ((b || c) && (XenoLibOutdated || ZeresPluginLibraryOutdated)),
let ret = ''; e = (() => {
if (XenoLibMissing || zlibMissing) ret += `Missing${XenoLibOutdated || ZeresPluginLibraryOutdated ? ' and outdated' : ''} `; let a = '';
else if (XenoLibOutdated || ZeresPluginLibraryOutdated) ret += `Outdated `; return b || c ? (a += `Missing${XenoLibOutdated || ZeresPluginLibraryOutdated ? ' and outdated' : ''} `) : (XenoLibOutdated || ZeresPluginLibraryOutdated) && (a += `Outdated `), (a += `${d ? 'Libraries' : 'Library'} `), a;
ret += `${bothLibsShit ? 'Libraries' : 'Library'} `; })(),
return ret; f = (() => {
})(); let a = `The ${d ? 'libraries' : 'library'} `;
const content = (() => { return b || XenoLibOutdated ? ((a += 'XenoLib '), (c || ZeresPluginLibraryOutdated) && (a += 'and ZeresPluginLibrary ')) : (c || ZeresPluginLibraryOutdated) && (a += 'ZeresPluginLibrary '), (a += `required for ${this.name} ${d ? 'are' : 'is'} ${b || c ? 'missing' : ''}${XenoLibOutdated || ZeresPluginLibraryOutdated ? (b || c ? ' and/or outdated' : 'outdated') : ''}.`), a;
let ret = `The ${bothLibsShit ? 'libraries' : 'library'} `; })(),
if (XenoLibMissing || XenoLibOutdated) { g = BdApi.findModuleByProps('push', 'update', 'pop', 'popWithKey'),
ret += 'XenoLib '; h = BdApi.findModuleByProps('Sizes', 'Weights'),
if (zlibMissing || ZeresPluginLibraryOutdated) ret += 'and ZeresPluginLibrary '; i = BdApi.findModule(a => a.defaultProps && a.key && 'confirm-modal' === a.key()),
} else if (zlibMissing || ZeresPluginLibraryOutdated) ret += 'ZeresPluginLibrary '; j = () => BdApi.getCore().alert(e, `${f}<br/>Due to a slight mishap however, you'll have to download the libraries yourself. After opening the links, do CTRL + S to download the library.<br/>${c || ZeresPluginLibraryOutdated ? '<br/><a href="http://betterdiscord.net/ghdl/?url=https://github.com/rauenzi/BDPluginLibrary/blob/master/release/0PluginLibrary.plugin.js"target="_blank">Click here to download ZeresPluginLibrary</a>' : ''}${b || XenoLibOutdated ? '<br/><a href="http://betterdiscord.net/ghdl/?url=https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js"target="_blank">Click here to download XenoLib</a>' : ''}`);
ret += `required for ${this.name} ${bothLibsShit ? 'are' : 'is'} ${XenoLibMissing || zlibMissing ? 'missing' : ''}${XenoLibOutdated || ZeresPluginLibraryOutdated ? (XenoLibMissing || zlibMissing ? ' and/or outdated' : 'outdated') : ''}.`; if (!g || !i || !h) return j();
return ret; class k extends BdApi.React.PureComponent {
})(); constructor(a) {
const ModalStack = BdApi.findModuleByProps('push', 'update', 'pop', 'popWithKey'); super(a), (this.state = { hasError: !1 });
const TextElement = BdApi.findModuleByProps('Sizes', 'Weights');
const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() === 'confirm-modal');
const onFail = () => BdApi.getCore().alert(header, `${content}<br/>Due to a slight mishap however, you'll have to download the libraries yourself. After opening the links, do CTRL + S to download the library.<br/>${zlibMissing || ZeresPluginLibraryOutdated ? '<br/><a href="http://betterdiscord.net/ghdl/?url=https://github.com/rauenzi/BDPluginLibrary/blob/master/release/0PluginLibrary.plugin.js"target="_blank">Click here to download ZeresPluginLibrary</a>' : ''}${XenoLibMissing || XenoLibOutdated ? '<br/><a href="http://betterdiscord.net/ghdl/?url=https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js"target="_blank">Click here to download XenoLib</a>' : ''}`);
if (!ModalStack || !ConfirmationModal || !TextElement) return onFail();
let modalId;
const onHeckWouldYouLookAtThat = (() => {
if (!global.pluginModule || !global.BDEvents) return;
if (XenoLibMissing || XenoLibOutdated) {
const listener = () => {
BDEvents.off('xenolib-loaded', listener);
ModalStack.popWithKey(modalId); /* make it easier on the user */
pluginModule.reloadPlugin(this.name);
};
BDEvents.on('xenolib-loaded', listener);
return () => BDEvents.off('xenolib-loaded', listener);
} }
const onLibLoaded = e => { componentDidCatch(a) {
if (e !== 'ZeresPluginLibrary') return; console.error(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`), this.setState({ hasError: !0 }), 'function' == typeof this.props.onError && this.props.onError(a);
BDEvents.off('plugin-loaded', onLibLoaded);
BDEvents.off('plugin-reloaded', onLibLoaded);
ModalStack.popWithKey(modalId); /* make it easier on the user */
pluginModule.reloadPlugin(this.name);
};
BDEvents.on('plugin-loaded', onLibLoaded);
BDEvents.on('plugin-reloaded', onLibLoaded);
return () => (BDEvents.off('plugin-loaded', onLibLoaded), BDEvents.off('plugin-reloaded', onLibLoaded));
})();
class TempErrorBoundary extends BdApi.React.PureComponent {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(err, inf) {
console.error(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`);
this.setState({ hasError: true });
if (typeof this.props.onError === 'function') this.props.onError(err);
} }
render() { render() {
if (this.state.hasError) return null; return this.state.hasError ? null : this.props.children;
return this.props.children;
} }
} }
modalId = ModalStack.push(props => { class l extends i {
return BdApi.React.createElement( submitModal() {
TempErrorBoundary, this.props.onConfirm();
{ }
label: 'missing dependency modal', }
onError: () => { let m = !1;
ModalStack.popWithKey(modalId); /* smh... */ const n = g.push(
onFail(); a =>
}
},
BdApi.React.createElement( BdApi.React.createElement(
ConfirmationModal, k,
Object.assign( {
{ label: 'missing dependency modal',
header, onError: () => {
children: [BdApi.React.createElement(TextElement, { color: TextElement.Colors.PRIMARY, children: [`${content} Please click Download Now to download ${bothLibsShit ? 'them' : 'it'}.`] })], g.popWithKey(n), j();
red: false, }
confirmText: 'Download Now', },
cancelText: 'Cancel', BdApi.React.createElement(
onConfirm: () => { l,
onHeckWouldYouLookAtThat(); Object.assign(
const request = require('request'); {
const fs = require('fs'); header: e,
const path = require('path'); children: [BdApi.React.createElement(h, { color: h.Colors.PRIMARY, children: [`${f} Please click Download Now to download ${d ? 'them' : 'it'}.`] })],
const waitForLibLoad = callback => { red: !1,
if (!global.BDEvents) return callback(); confirmText: 'Download Now',
const onLibLoaded = e => { cancelText: 'Cancel',
if (e !== 'ZeresPluginLibrary') return; onConfirm: () => {
BDEvents.off('plugin-loaded', onLibLoaded); if (m) return;
BDEvents.off('plugin-reloaded', onLibLoaded); m = !0;
callback(); const a = require('request'),
}; b = require('fs'),
BDEvents.on('plugin-loaded', onLibLoaded); c = require('path'),
BDEvents.on('plugin-reloaded', onLibLoaded); d = () => {
}; (global.XenoLib && !XenoLibOutdated) || a('https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js', (a, d, e) => (a ? j() : void b.writeFile(c.join(window.ContentManager.pluginsFolder, '1XenoLib.plugin.js'), e, () => {})));
const onDone = () => { };
if (!global.pluginModule || (!global.BDEvents && !global.XenoLib)) return; !global.ZeresPluginLibrary || ZeresPluginLibraryOutdated ? a('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (a, e, f) => (a ? j() : void (b.writeFile(c.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), f, () => {}), d()))) : d();
if ((global.XenoLib && !XenoLibOutdated) || !global.BDEvents /* yolo */) return pluginModule.reloadPlugin(this.name); }
const listener = () => { },
BDEvents.off('xenolib-loaded', listener); a
pluginModule.reloadPlugin(this.name); )
};
BDEvents.on('xenolib-loaded', listener);
};
const downloadXenoLib = () => {
if (global.XenoLib && !XenoLibOutdated) return onDone();
request('https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js', (error, response, body) => {
if (error) return onFail();
onDone();
fs.writeFile(path.join(window.ContentManager.pluginsFolder, '1XenoLib.plugin.js'), body, () => {});
});
};
if (!global.ZeresPluginLibrary || ZeresPluginLibraryOutdated) {
request('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (error, response, body) => {
if (error) return onFail();
waitForLibLoad(downloadXenoLib);
fs.writeFile(path.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), body, () => {});
});
} else downloadXenoLib();
}
},
props
) )
) ),
); void 0,
}); `${this.name}_DEP_MODAL`
);
} }
start() {} start() {}

View File

@ -1,4 +1,7 @@
# [BetterImageViewer](https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterImageViewer "BetterImageViewer") Changelog # [BetterImageViewer](https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterImageViewer "BetterImageViewer") Changelog
### 1.1.2
- Improved performance when smoothing is disabled
### 1.1.0 & 1.1.1 ### 1.1.0 & 1.1.1
- *SOME* people kept asking, so here you go. - *SOME* people kept asking, so here you go.
- Added image zoom, simply activate by click and holding on the image. - Added image zoom, simply activate by click and holding on the image.