image-url.esm.mjs 19 KB

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