image-url.umd.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = global || self, global.SanityImageUrlBuilder = factory());
  5. })(this, (function () {
  6. function _extends() {
  7. _extends = Object.assign || function (target) {
  8. for (var i = 1; i < arguments.length; i++) {
  9. var source = arguments[i];
  10. for (var key in source) {
  11. if (Object.prototype.hasOwnProperty.call(source, key)) {
  12. target[key] = source[key];
  13. }
  14. }
  15. }
  16. return target;
  17. };
  18. return _extends.apply(this, arguments);
  19. }
  20. function _unsupportedIterableToArray(o, minLen) {
  21. if (!o) return;
  22. if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  23. var n = Object.prototype.toString.call(o).slice(8, -1);
  24. if (n === "Object" && o.constructor) n = o.constructor.name;
  25. if (n === "Map" || n === "Set") return Array.from(o);
  26. if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  27. }
  28. function _arrayLikeToArray(arr, len) {
  29. if (len == null || len > arr.length) len = arr.length;
  30. for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
  31. return arr2;
  32. }
  33. function _createForOfIteratorHelperLoose(o, allowArrayLike) {
  34. var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
  35. if (it) return (it = it.call(o)).next.bind(it);
  36. if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
  37. if (it) o = it;
  38. var i = 0;
  39. return function () {
  40. if (i >= o.length) return {
  41. done: true
  42. };
  43. return {
  44. done: false,
  45. value: o[i++]
  46. };
  47. };
  48. }
  49. throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  50. }
  51. var example = 'image-Tb9Ew8CXIwaY6R1kjMvI0uRR-2000x3000-jpg';
  52. function parseAssetId(ref) {
  53. var _ref$split = ref.split('-'),
  54. id = _ref$split[1],
  55. dimensionString = _ref$split[2],
  56. format = _ref$split[3];
  57. if (!id || !dimensionString || !format) {
  58. throw new Error("Malformed asset _ref '" + ref + "'. Expected an id like \"" + example + "\".");
  59. }
  60. var _dimensionString$spli = dimensionString.split('x'),
  61. imgWidthStr = _dimensionString$spli[0],
  62. imgHeightStr = _dimensionString$spli[1];
  63. var width = +imgWidthStr;
  64. var height = +imgHeightStr;
  65. var isValidAssetId = isFinite(width) && isFinite(height);
  66. if (!isValidAssetId) {
  67. throw new Error("Malformed asset _ref '" + ref + "'. Expected an id like \"" + example + "\".");
  68. }
  69. return {
  70. id: id,
  71. width: width,
  72. height: height,
  73. format: format
  74. };
  75. }
  76. var isRef = function isRef(src) {
  77. var source = src;
  78. return source ? typeof source._ref === 'string' : false;
  79. };
  80. var isAsset = function isAsset(src) {
  81. var source = src;
  82. return source ? typeof source._id === 'string' : false;
  83. };
  84. var isAssetStub = function isAssetStub(src) {
  85. var source = src;
  86. return source && source.asset ? typeof source.asset.url === 'string' : false;
  87. }; // Convert an asset-id, asset or image to an image record suitable for processing
  88. // eslint-disable-next-line complexity
  89. function parseSource(source) {
  90. if (!source) {
  91. return null;
  92. }
  93. var image;
  94. if (typeof source === 'string' && isUrl(source)) {
  95. // Someone passed an existing image url?
  96. image = {
  97. asset: {
  98. _ref: urlToId(source)
  99. }
  100. };
  101. } else if (typeof source === 'string') {
  102. // Just an asset id
  103. image = {
  104. asset: {
  105. _ref: source
  106. }
  107. };
  108. } else if (isRef(source)) {
  109. // We just got passed an asset directly
  110. image = {
  111. asset: source
  112. };
  113. } else if (isAsset(source)) {
  114. // If we were passed an image asset document
  115. image = {
  116. asset: {
  117. _ref: source._id || ''
  118. }
  119. };
  120. } else if (isAssetStub(source)) {
  121. // If we were passed a partial asset (`url`, but no `_id`)
  122. image = {
  123. asset: {
  124. _ref: urlToId(source.asset.url)
  125. }
  126. };
  127. } else if (typeof source.asset === 'object') {
  128. // Probably an actual image with materialized asset
  129. image = _extends({}, source);
  130. } else {
  131. // We got something that does not look like an image, or it is an image
  132. // that currently isn't sporting an asset.
  133. return null;
  134. }
  135. var img = source;
  136. if (img.crop) {
  137. image.crop = img.crop;
  138. }
  139. if (img.hotspot) {
  140. image.hotspot = img.hotspot;
  141. }
  142. return applyDefaults(image);
  143. }
  144. function isUrl(url) {
  145. return /^https?:\/\//.test("" + url);
  146. }
  147. function urlToId(url) {
  148. var parts = url.split('/').slice(-1);
  149. return ("image-" + parts[0]).replace(/\.([a-z]+)$/, '-$1');
  150. } // Mock crop and hotspot if image lacks it
  151. function applyDefaults(image) {
  152. if (image.crop && image.hotspot) {
  153. return image;
  154. } // We need to pad in default values for crop or hotspot
  155. var result = _extends({}, image);
  156. if (!result.crop) {
  157. result.crop = {
  158. left: 0,
  159. top: 0,
  160. bottom: 0,
  161. right: 0
  162. };
  163. }
  164. if (!result.hotspot) {
  165. result.hotspot = {
  166. x: 0.5,
  167. y: 0.5,
  168. height: 1.0,
  169. width: 1.0
  170. };
  171. }
  172. return result;
  173. }
  174. var SPEC_NAME_TO_URL_NAME_MAPPINGS = [['width', 'w'], ['height', 'h'], ['format', 'fm'], ['download', 'dl'], ['blur', 'blur'], ['sharpen', 'sharp'], ['invert', 'invert'], ['orientation', 'or'], ['minHeight', 'min-h'], ['maxHeight', 'max-h'], ['minWidth', 'min-w'], ['maxWidth', 'max-w'], ['quality', 'q'], ['fit', 'fit'], ['crop', 'crop'], ['saturation', 'sat'], ['auto', 'auto'], ['dpr', 'dpr'], ['pad', 'pad']];
  175. function urlForImage(options) {
  176. var spec = _extends({}, options || {});
  177. var source = spec.source;
  178. delete spec.source;
  179. var image = parseSource(source);
  180. if (!image) {
  181. throw new Error("Unable to resolve image URL from source (" + JSON.stringify(source) + ")");
  182. }
  183. var id = image.asset._ref || image.asset._id || '';
  184. var asset = parseAssetId(id); // Compute crop rect in terms of pixel coordinates in the raw source image
  185. var cropLeft = Math.round(image.crop.left * asset.width);
  186. var cropTop = Math.round(image.crop.top * asset.height);
  187. var crop = {
  188. left: cropLeft,
  189. top: cropTop,
  190. width: Math.round(asset.width - image.crop.right * asset.width - cropLeft),
  191. height: Math.round(asset.height - image.crop.bottom * asset.height - cropTop)
  192. }; // Compute hot spot rect in terms of pixel coordinates
  193. var hotSpotVerticalRadius = image.hotspot.height * asset.height / 2;
  194. var hotSpotHorizontalRadius = image.hotspot.width * asset.width / 2;
  195. var hotSpotCenterX = image.hotspot.x * asset.width;
  196. var hotSpotCenterY = image.hotspot.y * asset.height;
  197. var hotspot = {
  198. left: hotSpotCenterX - hotSpotHorizontalRadius,
  199. top: hotSpotCenterY - hotSpotVerticalRadius,
  200. right: hotSpotCenterX + hotSpotHorizontalRadius,
  201. bottom: hotSpotCenterY + hotSpotVerticalRadius
  202. }; // If irrelevant, or if we are requested to: don't perform crop/fit based on
  203. // the crop/hotspot.
  204. if (!(spec.rect || spec.focalPoint || spec.ignoreImageParams || spec.crop)) {
  205. spec = _extends({}, spec, fit({
  206. crop: crop,
  207. hotspot: hotspot
  208. }, spec));
  209. }
  210. return specToImageUrl(_extends({}, spec, {
  211. asset: asset
  212. }));
  213. } // eslint-disable-next-line complexity
  214. function specToImageUrl(spec) {
  215. var cdnUrl = (spec.baseUrl || 'https://cdn.sanity.io').replace(/\/+$/, '');
  216. var filename = spec.asset.id + "-" + spec.asset.width + "x" + spec.asset.height + "." + spec.asset.format;
  217. var baseUrl = cdnUrl + "/images/" + spec.projectId + "/" + spec.dataset + "/" + filename;
  218. var params = [];
  219. if (spec.rect) {
  220. // Only bother url with a crop if it actually crops anything
  221. var _spec$rect = spec.rect,
  222. left = _spec$rect.left,
  223. top = _spec$rect.top,
  224. width = _spec$rect.width,
  225. height = _spec$rect.height;
  226. var isEffectiveCrop = left !== 0 || top !== 0 || height !== spec.asset.height || width !== spec.asset.width;
  227. if (isEffectiveCrop) {
  228. params.push("rect=" + left + "," + top + "," + width + "," + height);
  229. }
  230. }
  231. if (spec.bg) {
  232. params.push("bg=" + spec.bg);
  233. }
  234. if (spec.focalPoint) {
  235. params.push("fp-x=" + spec.focalPoint.x);
  236. params.push("fp-y=" + spec.focalPoint.y);
  237. }
  238. var flip = [spec.flipHorizontal && 'h', spec.flipVertical && 'v'].filter(Boolean).join('');
  239. if (flip) {
  240. params.push("flip=" + flip);
  241. } // Map from spec name to url param name, and allow using the actual param name as an alternative
  242. SPEC_NAME_TO_URL_NAME_MAPPINGS.forEach(function (mapping) {
  243. var specName = mapping[0],
  244. param = mapping[1];
  245. if (typeof spec[specName] !== 'undefined') {
  246. params.push(param + "=" + encodeURIComponent(spec[specName]));
  247. } else if (typeof spec[param] !== 'undefined') {
  248. params.push(param + "=" + encodeURIComponent(spec[param]));
  249. }
  250. });
  251. if (params.length === 0) {
  252. return baseUrl;
  253. }
  254. return baseUrl + "?" + params.join('&');
  255. }
  256. function fit(source, spec) {
  257. var cropRect;
  258. var imgWidth = spec.width;
  259. var imgHeight = spec.height; // If we are not constraining the aspect ratio, we'll just use the whole crop
  260. if (!(imgWidth && imgHeight)) {
  261. return {
  262. width: imgWidth,
  263. height: imgHeight,
  264. rect: source.crop
  265. };
  266. }
  267. var crop = source.crop;
  268. var hotspot = source.hotspot; // If we are here, that means aspect ratio is locked and fitting will be a bit harder
  269. var desiredAspectRatio = imgWidth / imgHeight;
  270. var cropAspectRatio = crop.width / crop.height;
  271. if (cropAspectRatio > desiredAspectRatio) {
  272. // The crop is wider than the desired aspect ratio. That means we are cutting from the sides
  273. var height = Math.round(crop.height);
  274. var width = Math.round(height * desiredAspectRatio);
  275. var top = Math.max(0, Math.round(crop.top)); // Center output horizontally over hotspot
  276. var hotspotXCenter = Math.round((hotspot.right - hotspot.left) / 2 + hotspot.left);
  277. var left = Math.max(0, Math.round(hotspotXCenter - width / 2)); // Keep output within crop
  278. if (left < crop.left) {
  279. left = crop.left;
  280. } else if (left + width > crop.left + crop.width) {
  281. left = crop.left + crop.width - width;
  282. }
  283. cropRect = {
  284. left: left,
  285. top: top,
  286. width: width,
  287. height: height
  288. };
  289. } else {
  290. // The crop is taller than the desired ratio, we are cutting from top and bottom
  291. var _width = crop.width;
  292. var _height = Math.round(_width / desiredAspectRatio);
  293. var _left = Math.max(0, Math.round(crop.left)); // Center output vertically over hotspot
  294. var hotspotYCenter = Math.round((hotspot.bottom - hotspot.top) / 2 + hotspot.top);
  295. var _top = Math.max(0, Math.round(hotspotYCenter - _height / 2)); // Keep output rect within crop
  296. if (_top < crop.top) {
  297. _top = crop.top;
  298. } else if (_top + _height > crop.top + crop.height) {
  299. _top = crop.top + crop.height - _height;
  300. }
  301. cropRect = {
  302. left: _left,
  303. top: _top,
  304. width: _width,
  305. height: _height
  306. };
  307. }
  308. return {
  309. width: imgWidth,
  310. height: imgHeight,
  311. rect: cropRect
  312. };
  313. } // For backwards-compatibility
  314. var validFits = ['clip', 'crop', 'fill', 'fillmax', 'max', 'scale', 'min'];
  315. var validCrops = ['top', 'bottom', 'left', 'right', 'center', 'focalpoint', 'entropy'];
  316. var validAutoModes = ['format'];
  317. function isSanityModernClientLike(client) {
  318. return client && 'config' in client ? typeof client.config === 'function' : false;
  319. }
  320. function isSanityClientLike(client) {
  321. return client && 'clientConfig' in client ? typeof client.clientConfig === 'object' : false;
  322. }
  323. function rewriteSpecName(key) {
  324. var specs = SPEC_NAME_TO_URL_NAME_MAPPINGS;
  325. for (var _iterator = _createForOfIteratorHelperLoose(specs), _step; !(_step = _iterator()).done;) {
  326. var entry = _step.value;
  327. var specName = entry[0],
  328. param = entry[1];
  329. if (key === specName || key === param) {
  330. return specName;
  331. }
  332. }
  333. return key;
  334. }
  335. function urlBuilder(options) {
  336. // Did we get a modernish client?
  337. if (isSanityModernClientLike(options)) {
  338. // Inherit config from client
  339. var _options$config = options.config(),
  340. apiUrl = _options$config.apiHost,
  341. projectId = _options$config.projectId,
  342. dataset = _options$config.dataset;
  343. var apiHost = apiUrl || 'https://api.sanity.io';
  344. return new ImageUrlBuilder(null, {
  345. baseUrl: apiHost.replace(/^https:\/\/api\./, 'https://cdn.'),
  346. projectId: projectId,
  347. dataset: dataset
  348. });
  349. } // Did we get a SanityClient?
  350. var client = options;
  351. if (isSanityClientLike(client)) {
  352. // Inherit config from client
  353. var _client$clientConfig = client.clientConfig,
  354. _apiUrl = _client$clientConfig.apiHost,
  355. _projectId = _client$clientConfig.projectId,
  356. _dataset = _client$clientConfig.dataset;
  357. var _apiHost = _apiUrl || 'https://api.sanity.io';
  358. return new ImageUrlBuilder(null, {
  359. baseUrl: _apiHost.replace(/^https:\/\/api\./, 'https://cdn.'),
  360. projectId: _projectId,
  361. dataset: _dataset
  362. });
  363. } // Or just accept the options as given
  364. return new ImageUrlBuilder(null, options);
  365. }
  366. var ImageUrlBuilder = /*#__PURE__*/function () {
  367. function ImageUrlBuilder(parent, options) {
  368. this.options = void 0;
  369. this.options = parent ? _extends({}, parent.options || {}, options || {}) // Merge parent options
  370. : _extends({}, options || {}); // Copy options
  371. }
  372. var _proto = ImageUrlBuilder.prototype;
  373. _proto.withOptions = function withOptions(options) {
  374. var baseUrl = options.baseUrl || this.options.baseUrl;
  375. var newOptions = {
  376. baseUrl: baseUrl
  377. };
  378. for (var key in options) {
  379. if (options.hasOwnProperty(key)) {
  380. var specKey = rewriteSpecName(key);
  381. newOptions[specKey] = options[key];
  382. }
  383. }
  384. return new ImageUrlBuilder(this, _extends({
  385. baseUrl: baseUrl
  386. }, newOptions));
  387. } // The image to be represented. Accepts a Sanity 'image'-document, 'asset'-document or
  388. // _id of asset. To get the benefit of automatic hot-spot/crop integration with the content
  389. // studio, the 'image'-document must be provided.
  390. ;
  391. _proto.image = function image(source) {
  392. return this.withOptions({
  393. source: source
  394. });
  395. } // Specify the dataset
  396. ;
  397. _proto.dataset = function dataset(_dataset2) {
  398. return this.withOptions({
  399. dataset: _dataset2
  400. });
  401. } // Specify the projectId
  402. ;
  403. _proto.projectId = function projectId(_projectId2) {
  404. return this.withOptions({
  405. projectId: _projectId2
  406. });
  407. } // Specify background color
  408. ;
  409. _proto.bg = function bg(_bg) {
  410. return this.withOptions({
  411. bg: _bg
  412. });
  413. } // Set DPR scaling factor
  414. ;
  415. _proto.dpr = function dpr(_dpr) {
  416. // A DPR of 1 is the default - so only include it if we have a different value
  417. return this.withOptions(_dpr && _dpr !== 1 ? {
  418. dpr: _dpr
  419. } : {});
  420. } // Specify the width of the image in pixels
  421. ;
  422. _proto.width = function width(_width) {
  423. return this.withOptions({
  424. width: _width
  425. });
  426. } // Specify the height of the image in pixels
  427. ;
  428. _proto.height = function height(_height) {
  429. return this.withOptions({
  430. height: _height
  431. });
  432. } // Specify focal point in fraction of image dimensions. Each component 0.0-1.0
  433. ;
  434. _proto.focalPoint = function focalPoint(x, y) {
  435. return this.withOptions({
  436. focalPoint: {
  437. x: x,
  438. y: y
  439. }
  440. });
  441. };
  442. _proto.maxWidth = function maxWidth(_maxWidth) {
  443. return this.withOptions({
  444. maxWidth: _maxWidth
  445. });
  446. };
  447. _proto.minWidth = function minWidth(_minWidth) {
  448. return this.withOptions({
  449. minWidth: _minWidth
  450. });
  451. };
  452. _proto.maxHeight = function maxHeight(_maxHeight) {
  453. return this.withOptions({
  454. maxHeight: _maxHeight
  455. });
  456. };
  457. _proto.minHeight = function minHeight(_minHeight) {
  458. return this.withOptions({
  459. minHeight: _minHeight
  460. });
  461. } // Specify width and height in pixels
  462. ;
  463. _proto.size = function size(width, height) {
  464. return this.withOptions({
  465. width: width,
  466. height: height
  467. });
  468. } // Specify blur between 0 and 100
  469. ;
  470. _proto.blur = function blur(_blur) {
  471. return this.withOptions({
  472. blur: _blur
  473. });
  474. };
  475. _proto.sharpen = function sharpen(_sharpen) {
  476. return this.withOptions({
  477. sharpen: _sharpen
  478. });
  479. } // Specify the desired rectangle of the image
  480. ;
  481. _proto.rect = function rect(left, top, width, height) {
  482. return this.withOptions({
  483. rect: {
  484. left: left,
  485. top: top,
  486. width: width,
  487. height: height
  488. }
  489. });
  490. } // Specify the image format of the image. 'jpg', 'pjpg', 'png', 'webp'
  491. ;
  492. _proto.format = function format(_format) {
  493. return this.withOptions({
  494. format: _format
  495. });
  496. };
  497. _proto.invert = function invert(_invert) {
  498. return this.withOptions({
  499. invert: _invert
  500. });
  501. } // Rotation in degrees 0, 90, 180, 270
  502. ;
  503. _proto.orientation = function orientation(_orientation) {
  504. return this.withOptions({
  505. orientation: _orientation
  506. });
  507. } // Compression quality 0-100
  508. ;
  509. _proto.quality = function quality(_quality) {
  510. return this.withOptions({
  511. quality: _quality
  512. });
  513. } // Make it a download link. Parameter is default filename.
  514. ;
  515. _proto.forceDownload = function forceDownload(download) {
  516. return this.withOptions({
  517. download: download
  518. });
  519. } // Flip image horizontally
  520. ;
  521. _proto.flipHorizontal = function flipHorizontal() {
  522. return this.withOptions({
  523. flipHorizontal: true
  524. });
  525. } // Flip image vertically
  526. ;
  527. _proto.flipVertical = function flipVertical() {
  528. return this.withOptions({
  529. flipVertical: true
  530. });
  531. } // Ignore crop/hotspot from image record, even when present
  532. ;
  533. _proto.ignoreImageParams = function ignoreImageParams() {
  534. return this.withOptions({
  535. ignoreImageParams: true
  536. });
  537. };
  538. _proto.fit = function fit(value) {
  539. if (validFits.indexOf(value) === -1) {
  540. throw new Error("Invalid fit mode \"" + value + "\"");
  541. }
  542. return this.withOptions({
  543. fit: value
  544. });
  545. };
  546. _proto.crop = function crop(value) {
  547. if (validCrops.indexOf(value) === -1) {
  548. throw new Error("Invalid crop mode \"" + value + "\"");
  549. }
  550. return this.withOptions({
  551. crop: value
  552. });
  553. } // Saturation
  554. ;
  555. _proto.saturation = function saturation(_saturation) {
  556. return this.withOptions({
  557. saturation: _saturation
  558. });
  559. };
  560. _proto.auto = function auto(value) {
  561. if (validAutoModes.indexOf(value) === -1) {
  562. throw new Error("Invalid auto mode \"" + value + "\"");
  563. }
  564. return this.withOptions({
  565. auto: value
  566. });
  567. } // Specify the number of pixels to pad the image
  568. ;
  569. _proto.pad = function pad(_pad) {
  570. return this.withOptions({
  571. pad: _pad
  572. });
  573. } // Gets the url based on the submitted parameters
  574. ;
  575. _proto.url = function url() {
  576. return urlForImage(this.options);
  577. } // Alias for url()
  578. ;
  579. _proto.toString = function toString() {
  580. return this.url();
  581. };
  582. return ImageUrlBuilder;
  583. }();
  584. return urlBuilder;
  585. }));
  586. //# sourceMappingURL=image-url.umd.js.map