Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1427036
ImageCarousel.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
ImageCarousel.js
View Options
const
View
=
require
(
'../mobile.startup/View'
),
util
=
require
(
'../mobile.startup/util'
),
mfExtend
=
require
(
'../mobile.startup/mfExtend'
),
IconButton
=
require
(
'../mobile.startup/IconButton'
),
icons
=
require
(
'../mobile.startup/icons'
),
eventBus
=
require
(
'../mobile.startup/eventBusSingleton'
),
Button
=
require
(
'../mobile.startup/Button'
),
detailsButton
=
new
Button
(
{
label
:
mw
.
msg
(
'mobile-frontend-media-details'
),
additionalClassNames
:
'button'
,
progressive
:
true
}
),
slideLeftButton
=
new
IconButton
(
{
rotation
:
90
,
icon
:
'expand-invert'
,
label
:
mw
.
msg
(
'mobile-frontend-media-prev'
)
}
),
slideRightButton
=
new
IconButton
(
{
rotation
:
-
90
,
icon
:
'expand-invert'
,
label
:
mw
.
msg
(
'mobile-frontend-media-next'
)
}
),
LoadErrorMessage
=
require
(
'./LoadErrorMessage'
),
ImageGateway
=
require
(
'./ImageGateway'
),
router
=
__non_webpack_require__
(
'mediawiki.router'
);
/**
* Displays images in full screen overlay
*
* @class ImageCarousel
* @extends module:mobile.startup/View
* @param {Object} options Configuration options, see Overlay#defaults
* @private
*/
function
ImageCarousel
(
options
)
{
this
.
gateway
=
options
.
gateway
||
new
ImageGateway
(
{
api
:
options
.
api
}
);
this
.
router
=
options
.
router
||
router
;
this
.
eventBus
=
options
.
eventBus
;
this
.
hasLoadError
=
false
;
View
.
call
(
this
,
util
.
extend
(
{
className
:
'image-carousel'
,
events
:
{
'click .image-wrapper'
:
'onToggleDetails'
,
// Click tracking for table of contents so we can see if people interact with it
'click .slider-button'
:
'onSlide'
}
},
options
)
);
}
mfExtend
(
ImageCarousel
,
View
,
{
/**
* @memberof ImageCarousel
* @instance
*/
template
:
util
.
template
(
`
<button title="{{prevMsg}}" class="prev slider-button"></button>
<div class="main">
<div class="image-wrapper">
<div class="image"></div>
</div>
<!-- cancel button will go here -->
<div class="image-details">
<!-- details button will go here -->
<p class="truncated-text">{{caption}}</p>
<p class="license"><a href="#">{{licenseLinkMsg}}</a></p>
</div>
</div>
<button title="{{nextMsg}}" class="next slider-button"></button>
`
),
/**
* @memberof ImageCarousel
* @instance
* @mixes Overlay#defaults
* @property {Object} defaults Default options hash.
* @property {mw.Api} defaults.api instance of API to use
* @property {string} defaults.licenseLinkMsg Link to license information in media viewer.
* @property {string} defaults.prevMsg Title for "prev" button in media viewer.
* @property {string} defaults.nextMsg Title for "next" button in media viewer.
* @property {Thumbnail[]} defaults.thumbnails a list of thumbnails to browse
*/
defaults
:
util
.
extend
(
{},
View
.
prototype
.
defaults
,
{
licenseLinkMsg
:
mw
.
msg
(
'mobile-frontend-media-license-link'
),
prevMsg
:
mw
.
msg
(
'mobile-frontend-media-prev'
),
nextMsg
:
mw
.
msg
(
'mobile-frontend-media-next'
),
thumbnails
:
[]
}
),
/**
* Event handler for slide event
*
* @memberof ImageCarousel
* @instance
* @param {jQuery.Event} ev
*/
onSlide
:
function
(
ev
)
{
const
nextThumbnail
=
this
.
$el
.
find
(
ev
.
target
).
closest
(
'.slider-button'
).
data
(
'thumbnail'
),
title
=
nextThumbnail
.
options
.
filename
;
this
.
router
.
navigateTo
(
null
,
{
path
:
'#/media/'
+
title
,
useReplaceState
:
true
}
);
this
.
options
.
title
=
nextThumbnail
.
options
.
filename
;
const
newImageCarousel
=
new
ImageCarousel
(
this
.
options
);
this
.
$el
.
replaceWith
(
newImageCarousel
.
$el
);
this
.
$el
=
newImageCarousel
.
$el
;
},
/**
* @inheritdoc
* @memberof ImageCarousel
* @instance
*/
preRender
:
function
()
{
const
self
=
this
;
this
.
options
.
thumbnails
.
forEach
(
function
(
thumbnail
,
i
)
{
if
(
thumbnail
.
getFileName
()
===
self
.
options
.
title
)
{
self
.
options
.
caption
=
thumbnail
.
getDescription
();
self
.
galleryOffset
=
i
;
}
}
);
},
/**
* Setup the next and previous images to enable the user to arrow through
* all images in the set of images given in thumbs.
*
* @memberof ImageCarousel
* @instance
* @param {Array} thumbs A set of images, which are available
* @private
*/
_enableArrowImages
:
function
(
thumbs
)
{
const
offset
=
this
.
galleryOffset
;
let
lastThumb
,
nextThumb
;
if
(
this
.
galleryOffset
===
undefined
)
{
// couldn't find a suitable matching thumbnail so make
// next slide start at beginning and previous slide be end
lastThumb
=
thumbs
[
thumbs
.
length
-
1
];
nextThumb
=
thumbs
[
0
];
}
else
{
// identify last thumbnail
lastThumb
=
thumbs
[
offset
===
0
?
thumbs
.
length
-
1
:
offset
-
1
];
nextThumb
=
thumbs
[
offset
===
thumbs
.
length
-
1
?
0
:
offset
+
1
];
}
this
.
$el
.
find
(
'.prev'
).
data
(
'thumbnail'
,
lastThumb
);
this
.
$el
.
find
(
'.next'
).
data
(
'thumbnail'
,
nextThumb
);
},
/**
* Disables the possibility to arrow through all images of the page.
*
* @memberof ImageCarousel
* @instance
* @private
*/
_disableArrowImages
:
function
()
{
this
.
$el
.
find
(
'.prev, .next'
).
remove
();
},
/**
* Handler for retry event which triggers when user tries to reload overlay
* after a loading error.
*
* @memberof ImageCarousel
* @instance
* @private
*/
_handleRetry
:
function
()
{
// A hacky way to simulate a reload of the overlay
this
.
router
.
emit
(
'hashchange'
);
},
/**
* @inheritdoc
* @memberof ImageCarousel
* @instance
*/
postRender
:
function
()
{
let
$img
;
const
$el
=
this
.
$el
,
$spinner
=
icons
.
spinner
().
$el
,
thumbs
=
this
.
options
.
thumbnails
||
[],
self
=
this
;
/**
* Display media load failure message
*
* @method
* @ignore
*/
function
showLoadFailMsg
()
{
self
.
hasLoadError
=
true
;
$spinner
.
hide
();
// hide broken image if present
$el
.
find
(
'.image img'
).
hide
();
// show error message if not visible already
if
(
$el
.
find
(
'.load-fail-msg'
).
length
===
0
)
{
new
LoadErrorMessage
(
{
retryPath
:
self
.
router
.
getPath
()
}
)
.
on
(
'retry'
,
self
.
_handleRetry
.
bind
(
self
)
)
.
prependTo
(
$el
.
find
(
'.image'
)
);
}
}
/**
* Start image load transitions
*
* @method
* @ignore
*/
function
addImageLoadClass
()
{
$img
.
addClass
(
'image-loaded'
);
}
if
(
thumbs
.
length
<
2
)
{
this
.
_disableArrowImages
();
}
else
{
this
.
_enableArrowImages
(
thumbs
);
}
this
.
$details
=
$el
.
find
(
'.image-details'
);
$el
.
find
(
'.image'
).
append
(
$spinner
);
this
.
$details
.
prepend
(
detailsButton
.
$el
);
this
.
gateway
.
getThumb
(
self
.
options
.
title
).
then
(
function
(
data
)
{
let
author
;
const
url
=
data
.
descriptionurl
+
'#mw-jump-to-license'
;
$spinner
.
hide
();
self
.
thumbWidth
=
data
.
thumbwidth
;
self
.
thumbHeight
=
data
.
thumbheight
;
self
.
imgRatio
=
data
.
thumbwidth
/
data
.
thumbheight
;
// We need to explicitly specify document for context param as jQuery 3
// will create a new document for the element if the context is
// undefined. If element is appended to active document, event handlers
// can fire in both the active document and new document which can cause
// insidious bugs.
// (https://api.jquery.com/jquery.parsehtml/#entry-longdesc)
$img
=
self
.
parseHTML
(
'<img>'
,
document
);
// Remove the loader when the image is loaded or display load fail
// message on failure
//
// Error event handler must be attached before error occurs
// (https://api.jquery.com/error/#entry-longdesc)
//
// For the load event, it is more unclear what happens cross-browser when
// the image is loaded from cache. It seems that a .complete check is
// needed if attaching the load event after setting the src.
// (http://stackoverflow.com/questions/910727/jquery-event-for-images-loaded#comment10616132_1110094)
//
// However, perhaps .complete check is not needed if attaching load
// event prior to setting the image src
// (https://stackoverflow.com/questions/12354865/image-onload-event-and-browser-cache#answer-12355031)
$img
.
on
(
'load'
,
addImageLoadClass
).
on
(
'error'
,
showLoadFailMsg
);
$img
.
attr
(
'src'
,
data
.
thumburl
).
attr
(
'alt'
,
self
.
options
.
caption
);
$el
.
find
(
'.image'
).
append
(
$img
);
self
.
$details
.
addClass
(
'is-visible'
);
self
.
_positionImage
();
$el
.
find
(
'.image-details a'
).
attr
(
'href'
,
url
);
if
(
data
.
extmetadata
)
{
// Add license information
if
(
data
.
extmetadata
.
LicenseShortName
)
{
$el
.
find
(
'.license a'
)
.
text
(
data
.
extmetadata
.
LicenseShortName
.
value
)
.
attr
(
'href'
,
url
);
}
// Add author information
if
(
data
.
extmetadata
.
Artist
)
{
// Strip any tags
author
=
data
.
extmetadata
.
Artist
.
value
.
replace
(
/<.*?>/g
,
''
);
$el
.
find
(
'.license'
).
prepend
(
author
+
' • '
);
}
}
self
.
adjustDetails
();
},
function
()
{
// retrieving image location failed so show load fail msg
showLoadFailMsg
();
}
);
eventBus
.
on
(
'resize:throttled'
,
this
.
_positionImage
.
bind
(
this
)
);
this
.
_positionImage
();
},
/**
* Event handler that toggles the details bar.
*
* @memberof ImageCarousel
* @instance
*/
onToggleDetails
:
function
()
{
if
(
!
this
.
hasLoadError
)
{
this
.
$el
.
find
(
'.cancel, .slider-button'
).
toggle
();
this
.
$details
.
toggle
();
this
.
_positionImage
();
}
},
/**
* Fit the image into the window if its dimensions are bigger than the window dimensions.
* Compare window width to height ratio to that of image width to height when setting
* image width or height.
*
* @memberof ImageCarousel
* @instance
* @private
*/
_positionImage
:
function
()
{
const
$window
=
util
.
getWindow
();
this
.
adjustDetails
();
// with a hidden details box we have a little bit more space, we just need to use it
// TODO: Get visibility from the model
// eslint-disable-next-line no-jquery/no-sizzle
const
detailsHeight
=
!
this
.
$details
.
is
(
':visible'
)
?
0
:
this
.
$details
.
outerHeight
();
const
windowWidth
=
$window
.
width
();
const
windowHeight
=
$window
.
height
()
-
detailsHeight
;
const
windowRatio
=
windowWidth
/
windowHeight
;
const
$img
=
this
.
$el
.
find
(
'img'
);
if
(
this
.
imgRatio
>
windowRatio
)
{
if
(
windowWidth
<
this
.
thumbWidth
)
{
$img
.
css
(
{
width
:
windowWidth
,
height
:
'auto'
}
);
}
}
else
{
if
(
windowHeight
<
this
.
thumbHeight
)
{
$img
.
css
(
{
width
:
'auto'
,
height
:
windowHeight
}
);
}
}
this
.
$el
.
find
(
'.image-wrapper'
).
css
(
'bottom'
,
detailsHeight
);
this
.
$el
.
find
(
'.slider-button.prev'
).
append
(
slideLeftButton
.
$el
);
this
.
$el
.
find
(
'.slider-button.next'
).
append
(
slideRightButton
.
$el
);
},
/**
* Function to adjust the height of details section to not more than 50% of window height.
*
* @memberof ImageCarousel
* @instance
*/
adjustDetails
:
function
()
{
const
windowHeight
=
util
.
getWindow
().
height
();
if
(
this
.
$el
.
find
(
'.image-details'
).
height
()
>
windowHeight
*
0.50
)
{
this
.
$el
.
find
(
'.image-details'
).
css
(
'max-height'
,
windowHeight
*
0.50
);
}
}
}
);
module
.
exports
=
ImageCarousel
;
File Metadata
Details
Attached
Mime Type
text/html
Expires
Sat, May 16, 14:06 (1 d, 14 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
37/ce/29f8f54dfd28777470fcb5f4b2c9
Default Alt Text
ImageCarousel.js (10 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment