mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-05 11:19:34 +00:00
[add] Image source headers handling
Extend ImageLoader functionality to be able to work with image sources containing headers We preserve the existing strategy that works with image.src for cases where source is just an uri with no headers When sources contain headers we make a fetch request and then render a local url for the downloaded blob (URL.createObjectURL) Fix #1019 Fix #2268 Close #2442
This commit is contained in:
committed by
Nicolas Gallagher
parent
39b94b1945
commit
36f46b3510
@@ -15,6 +15,18 @@ const dataBase64Svg =
|
|||||||
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMjAwJyBoZWlnaHQ9JzIwMCcgZmlsbD0iIzAwMDAwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGQ9Ik0yNS44NjcsNDguODUzQzMyLjgwNiw1MC4xNzYsNDYuNDYsNTIuNSw2MS4yMTUsNTIuNWgwLjAwNWM5LjcxLDAsMTguNDAxLTEuMDU3LDI1LjkzOC0yLjkxMyAgIGMwLjE1OS0wLjA0NiwwLjM1LTAuMTM1LDAuNTY1LTAuMTg3YzAuMjgyLTAuMDcyLDAuNTY1LTAuMTY0LDAuODQ0LTAuMjM4YzMuMTg0LTAuOTY0LDIuNTc3LTMuMDUxLDIuMTk5LTMuODUyICAgYy00LjE2Ni03LjcxOS0xNS4wODYtMjMuNDE1LTM1LjAyOC0yMy40MTVjLTIyLjE2OSwwLTMwLjI2MiwxMC42MzUtMzMuMTQsMTkuNTg5QzIyLjU0NSw0Mi4zMzMsMjIuNDA3LDQ3LjEzNSwyNS44NjcsNDguODUzeiAgICBNMjguNjc2LDM4LjAzMmMwLjAxMy0wLjAzNiwwLjYxNC0xLjYyNiwxLjkyMy0xLjAwOGMxLjEzMywwLjUzNSwwLjk2MSwxLjU2MywwLjg4NywxLjg1Yy0wLjAwNywwLjAyNC0wLjAxNCwwLjA0OC0wLjAyMSwwLjA3MyAgIGMwLDAuMDAxLTAuMDAxLDAuMDA0LTAuMDAxLDAuMDA0bDAsMGMtMC4yNDksMC45MjktMC40MDQsMi4wODYtMC4wMTcsMi44NmMwLjE2LDAuMzE5LDAuNDkyLDAuNzY4LDEuNTQyLDAuOTg3bDAuMzY2LDAuMDc3ICAgYzIwLjgxNiw0LjM2LDM2LDIuOTMzLDQ1LjY3OCwwLjYyNmwtMC4wMDQsMC4wMDJjMCwwLDAuMDA1LTAuMDAyLDAuMDA3LTAuMDAzYzAuMjEyLTAuMDUsMC40MjEtMC4xMDEsMC42MjgtMC4xNTIgICBjMC41MDktMC4wNSwxLjE3MywwLjA3OCwxLjM5OSwxYzAuMzUxLDEuNDI0LTAuOTczLDEuODk1LTEuMjE3LDEuOTY5Yy01LjMyNSwxLjI3OS0xMi4yNjYsMi4zMDYtMjAuODM1LDIuMzA3ICAgYy03LjUwNSwwLTE2LjI1NS0wLjc4Ny0yNi4yNTctMi44ODJsLTAuMzY0LTAuMDc3Yy0yLjEyLTAuNDQyLTMuMTExLTEuNjMzLTMuNTY5LTIuNTU1QzI3Ljk4NSw0MS40MjEsMjguMjgxLDM5LjQxNiwyOC42NzYsMzguMDMyICAgeiI+PC9wYXRoPjxjaXJjbGUgY3g9IjEwLjQ5MyIgY3k9IjIzLjQ1NSIgcj0iMC42MTkiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0yLjA4LDI4LjMwOGMwLjY3Ni0wLjE3OCwwLjk4My0wLjM1MiwxLjE3NC0wLjVDNC42OSwyNi42OSw2LjUsMjcuNDgzLDcuNSwyOC4zNTd2MC4wMDJjMCwwLDEuNzExLDEuMjM1LDAuNzM3LDIuMjAyICAgYy0wLjk3NCwwLjk2NS0yLjMxOSwwLjAwNi0yLjMxOSwwLjAwNmwwLjAzNSwwLjAxNmMtMC4zMjctMC4yMDMtMC42LTAuNTYxLTAuNzgtMC41ODRjLTAuMzcsMC4yNi0wLjg3NiwwLjUtMS40NzYsMC41SDMuNyAgIGMwLDAtMS4zNDUsMC43MDksMC4xNzgsMS42NTJjMC4wMDEsMC4wMDEsMC4wMDIsMC4wNzIsMC4wMDQsMC4wNzNjMy45MzksMi4zNDIsOC4yNzEsNS43MDEsOC4yNzEsOC44OCAgIGMwLDAuNjkxLDAuMiwxNy4wNDIsMTcuNjI2LDI0LjczOWwwLjk2NywwLjQ0MmwtMC4xLDEuMDU5Yy0wLjQyMSw0LjM5LDEuMTQ1LDEwLjE5MSwxMC45OTMsMTIuODg4bDAuMTEzLDAuMDM4ICAgYzAuMDY3LDAuMDIzLDYuNzMyLDIuNDI5LDEwLjkwNywyLjQyOWMxLjU4NCwwLDIuMTU1LTAuMzUyLDIuMjQzLTAuNTYxYzAuMDg1LTAuMjAyLDAuNjEyLTIuMTY0LTYuMzMyLTkuMzg3bDAuMDAyLTAuMTgzICAgYzAsMC0yLjQ3Ny0zLjA3LDEuNTMzLTMuMDdjMC4wMSwwLDAuMDE5LDAsMC4wMjksMGMxLjI4NSwwLDIuNjA4LDAuMjE1LDMuOTgsMC4xODRjNC43NzEtMC4xMTcsOS4zMTYtMC40MjUsMTMuNTA2LTEuMDk2ICAgbDAuNDc0LTAuMDI4bDAuNjY4LDAuMTU4YzkuNjUxLDQuOTQ4LDE2LjczOCw3LjcxNiwxOS43MzgsNy43MTZ2MC4wMDZjMCwwLDAuMTY0LDAuMDExLDAuMjMsMC4wMDQgICBjLTAuMTg5LTAuNzIzLTIuMjMtMi44LTcuMjMtOS4wNzl2MC4wMjFjMCwwLTEuNTEyLTEuNjU4LDAuNzk3LTIuNjUzYzAuMDYzLTAuMDI2LDAuMDA4LDAuMDIzLDAuMDYtMC4wMDEgICBjOC42MzktMy41MDksMTMuNTAxLTguMjA0LDE1LjQxMS0xMS43NzVjMS4xNDUtMi4xMjksMC4yMDYtMi43ODQtMC42NTktMi45NzZjLTAuMzE3LTAuMDM4LTAuNjM0LTAuMDYyLTAuOTEyLTAuMDYyICAgYy0wLjIwNSwwLTAuMzc5LDAuMDEtMC41MjgsMC4wMjdsLTMuMTQzLDEuMjE0QzgzLjczMiw1My45MjYsNzMuMjE4LDU1LjUsNjEuMjIsNTUuNWMtMC4wMDIsMC0wLjAwNSwwLTAuMDA1LDAgICBjLTE1LjEyOCwwLTI5LjEwMS0yLjQzMi0zNi4wODMtMy43NzFsLTAuMTczLTAuMTExbC0wLjE2LTAuMTI2Yy01Ljg1OC0yLjY4MS01LjEzNy0xMC4yMDItNS4xMDMtMTAuNTE5bDAuMDYtMC4zICAgYzAuODk1LTIuODM4LDIuNDY3LTYuMzUyLDUuMjEzLTkuNzE5Yy0xLjgwOC0xLjM2OS00LjU5LTQuMTg4LTQuNDMtOC40OTRjMC4wNDYtMS4yNDQtMC40ODYtMi41MDgtMS40OTgtMy41NTkgICBjLTEuNDk4LTEuNTU1LTMuNzg1LTIuNDQ2LTYuMjc0LTIuNDQ2Yy0xLjc3LDAtMy41NTMsMC40NDItNS4yOTMsMS4zMTRjLTQuMDYxLDIuMDM1LTQuODU1LDQuNzM2LTUuNjkyLDcuNTk2ICAgYy0wLjEzNiwwLjQ2OC0wLjI4NCwwLjkzOS0wLjQzOCwxLjQxYy0wLjAwNiwwLjAxOS0wLjAyMiwwLjAzNS0wLjAyOCwwLjA1NkMwLjgzMywyOC40MjMsMS42OTEsMjguMzksMi4wOCwyOC4zMDh6IE0xMC40OTMsMTkuOTA4ICAgYzEuOTU2LDAsMy41NDgsMS41OTEsMy41NDgsMy41NDdjMCwxLjk1Ny0xLjU5MiwzLjU0OC0zLjU0OCwzLjU0OGMtMS45NTcsMC0zLjU0OC0xLjU5Mi0zLjU0OC0zLjU0OCAgIEM2Ljk0NCwyMS40OTksOC41MzYsMTkuOTA4LDEwLjQ5MywxOS45MDh6Ij48L3BhdGg+PC9nPjwvc3ZnPg==';
|
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMjAwJyBoZWlnaHQ9JzIwMCcgZmlsbD0iIzAwMDAwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGQ9Ik0yNS44NjcsNDguODUzQzMyLjgwNiw1MC4xNzYsNDYuNDYsNTIuNSw2MS4yMTUsNTIuNWgwLjAwNWM5LjcxLDAsMTguNDAxLTEuMDU3LDI1LjkzOC0yLjkxMyAgIGMwLjE1OS0wLjA0NiwwLjM1LTAuMTM1LDAuNTY1LTAuMTg3YzAuMjgyLTAuMDcyLDAuNTY1LTAuMTY0LDAuODQ0LTAuMjM4YzMuMTg0LTAuOTY0LDIuNTc3LTMuMDUxLDIuMTk5LTMuODUyICAgYy00LjE2Ni03LjcxOS0xNS4wODYtMjMuNDE1LTM1LjAyOC0yMy40MTVjLTIyLjE2OSwwLTMwLjI2MiwxMC42MzUtMzMuMTQsMTkuNTg5QzIyLjU0NSw0Mi4zMzMsMjIuNDA3LDQ3LjEzNSwyNS44NjcsNDguODUzeiAgICBNMjguNjc2LDM4LjAzMmMwLjAxMy0wLjAzNiwwLjYxNC0xLjYyNiwxLjkyMy0xLjAwOGMxLjEzMywwLjUzNSwwLjk2MSwxLjU2MywwLjg4NywxLjg1Yy0wLjAwNywwLjAyNC0wLjAxNCwwLjA0OC0wLjAyMSwwLjA3MyAgIGMwLDAuMDAxLTAuMDAxLDAuMDA0LTAuMDAxLDAuMDA0bDAsMGMtMC4yNDksMC45MjktMC40MDQsMi4wODYtMC4wMTcsMi44NmMwLjE2LDAuMzE5LDAuNDkyLDAuNzY4LDEuNTQyLDAuOTg3bDAuMzY2LDAuMDc3ICAgYzIwLjgxNiw0LjM2LDM2LDIuOTMzLDQ1LjY3OCwwLjYyNmwtMC4wMDQsMC4wMDJjMCwwLDAuMDA1LTAuMDAyLDAuMDA3LTAuMDAzYzAuMjEyLTAuMDUsMC40MjEtMC4xMDEsMC42MjgtMC4xNTIgICBjMC41MDktMC4wNSwxLjE3MywwLjA3OCwxLjM5OSwxYzAuMzUxLDEuNDI0LTAuOTczLDEuODk1LTEuMjE3LDEuOTY5Yy01LjMyNSwxLjI3OS0xMi4yNjYsMi4zMDYtMjAuODM1LDIuMzA3ICAgYy03LjUwNSwwLTE2LjI1NS0wLjc4Ny0yNi4yNTctMi44ODJsLTAuMzY0LTAuMDc3Yy0yLjEyLTAuNDQyLTMuMTExLTEuNjMzLTMuNTY5LTIuNTU1QzI3Ljk4NSw0MS40MjEsMjguMjgxLDM5LjQxNiwyOC42NzYsMzguMDMyICAgeiI+PC9wYXRoPjxjaXJjbGUgY3g9IjEwLjQ5MyIgY3k9IjIzLjQ1NSIgcj0iMC42MTkiPjwvY2lyY2xlPjxwYXRoIGQ9Ik0yLjA4LDI4LjMwOGMwLjY3Ni0wLjE3OCwwLjk4My0wLjM1MiwxLjE3NC0wLjVDNC42OSwyNi42OSw2LjUsMjcuNDgzLDcuNSwyOC4zNTd2MC4wMDJjMCwwLDEuNzExLDEuMjM1LDAuNzM3LDIuMjAyICAgYy0wLjk3NCwwLjk2NS0yLjMxOSwwLjAwNi0yLjMxOSwwLjAwNmwwLjAzNSwwLjAxNmMtMC4zMjctMC4yMDMtMC42LTAuNTYxLTAuNzgtMC41ODRjLTAuMzcsMC4yNi0wLjg3NiwwLjUtMS40NzYsMC41SDMuNyAgIGMwLDAtMS4zNDUsMC43MDksMC4xNzgsMS42NTJjMC4wMDEsMC4wMDEsMC4wMDIsMC4wNzIsMC4wMDQsMC4wNzNjMy45MzksMi4zNDIsOC4yNzEsNS43MDEsOC4yNzEsOC44OCAgIGMwLDAuNjkxLDAuMiwxNy4wNDIsMTcuNjI2LDI0LjczOWwwLjk2NywwLjQ0MmwtMC4xLDEuMDU5Yy0wLjQyMSw0LjM5LDEuMTQ1LDEwLjE5MSwxMC45OTMsMTIuODg4bDAuMTEzLDAuMDM4ICAgYzAuMDY3LDAuMDIzLDYuNzMyLDIuNDI5LDEwLjkwNywyLjQyOWMxLjU4NCwwLDIuMTU1LTAuMzUyLDIuMjQzLTAuNTYxYzAuMDg1LTAuMjAyLDAuNjEyLTIuMTY0LTYuMzMyLTkuMzg3bDAuMDAyLTAuMTgzICAgYzAsMC0yLjQ3Ny0zLjA3LDEuNTMzLTMuMDdjMC4wMSwwLDAuMDE5LDAsMC4wMjksMGMxLjI4NSwwLDIuNjA4LDAuMjE1LDMuOTgsMC4xODRjNC43NzEtMC4xMTcsOS4zMTYtMC40MjUsMTMuNTA2LTEuMDk2ICAgbDAuNDc0LTAuMDI4bDAuNjY4LDAuMTU4YzkuNjUxLDQuOTQ4LDE2LjczOCw3LjcxNiwxOS43MzgsNy43MTZ2MC4wMDZjMCwwLDAuMTY0LDAuMDExLDAuMjMsMC4wMDQgICBjLTAuMTg5LTAuNzIzLTIuMjMtMi44LTcuMjMtOS4wNzl2MC4wMjFjMCwwLTEuNTEyLTEuNjU4LDAuNzk3LTIuNjUzYzAuMDYzLTAuMDI2LDAuMDA4LDAuMDIzLDAuMDYtMC4wMDEgICBjOC42MzktMy41MDksMTMuNTAxLTguMjA0LDE1LjQxMS0xMS43NzVjMS4xNDUtMi4xMjksMC4yMDYtMi43ODQtMC42NTktMi45NzZjLTAuMzE3LTAuMDM4LTAuNjM0LTAuMDYyLTAuOTEyLTAuMDYyICAgYy0wLjIwNSwwLTAuMzc5LDAuMDEtMC41MjgsMC4wMjdsLTMuMTQzLDEuMjE0QzgzLjczMiw1My45MjYsNzMuMjE4LDU1LjUsNjEuMjIsNTUuNWMtMC4wMDIsMC0wLjAwNSwwLTAuMDA1LDAgICBjLTE1LjEyOCwwLTI5LjEwMS0yLjQzMi0zNi4wODMtMy43NzFsLTAuMTczLTAuMTExbC0wLjE2LTAuMTI2Yy01Ljg1OC0yLjY4MS01LjEzNy0xMC4yMDItNS4xMDMtMTAuNTE5bDAuMDYtMC4zICAgYzAuODk1LTIuODM4LDIuNDY3LTYuMzUyLDUuMjEzLTkuNzE5Yy0xLjgwOC0xLjM2OS00LjU5LTQuMTg4LTQuNDMtOC40OTRjMC4wNDYtMS4yNDQtMC40ODYtMi41MDgtMS40OTgtMy41NTkgICBjLTEuNDk4LTEuNTU1LTMuNzg1LTIuNDQ2LTYuMjc0LTIuNDQ2Yy0xLjc3LDAtMy41NTMsMC40NDItNS4yOTMsMS4zMTRjLTQuMDYxLDIuMDM1LTQuODU1LDQuNzM2LTUuNjkyLDcuNTk2ICAgYy0wLjEzNiwwLjQ2OC0wLjI4NCwwLjkzOS0wLjQzOCwxLjQxYy0wLjAwNiwwLjAxOS0wLjAyMiwwLjAzNS0wLjAyOCwwLjA1NkMwLjgzMywyOC40MjMsMS42OTEsMjguMzksMi4wOCwyOC4zMDh6IE0xMC40OTMsMTkuOTA4ICAgYzEuOTU2LDAsMy41NDgsMS41OTEsMy41NDgsMy41NDdjMCwxLjk1Ny0xLjU5MiwzLjU0OC0zLjU0OCwzLjU0OGMtMS45NTcsMC0zLjU0OC0xLjU5Mi0zLjU0OC0zLjU0OCAgIEM2Ljk0NCwyMS40OTksOC41MzYsMTkuOTA4LDEwLjQ5MywxOS45MDh6Ij48L3BhdGg+PC9nPjwvc3ZnPg==';
|
||||||
const dataSvg =
|
const dataSvg =
|
||||||
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>';
|
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>';
|
||||||
|
const sourceWithHeaders = {
|
||||||
|
uri: placeholder,
|
||||||
|
headers: {
|
||||||
|
'x-token': '0012345'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const sourceWithHeadersAndRedirect = {
|
||||||
|
uri: source,
|
||||||
|
headers: {
|
||||||
|
'x-token': '0012345'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function Divider() {
|
function Divider() {
|
||||||
return <View style={styles.divider} />;
|
return <View style={styles.divider} />;
|
||||||
@@ -118,6 +130,17 @@ export default function ImagePage() {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
<Divider />
|
||||||
|
<View style={styles.row}>
|
||||||
|
<View style={styles.column}>
|
||||||
|
<Text style={[styles.text]}>With Headers</Text>
|
||||||
|
<Image source={sourceWithHeaders} style={styles.image} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.column}>
|
||||||
|
<Text style={[styles.text]}>Headers & Redirect</Text>
|
||||||
|
<Image source={sourceWithHeadersAndRedirect} style={styles.image} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</Example>
|
</Example>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -329,14 +329,14 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
|
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
|
||||||
style="filter: url(#tint-55);"
|
style="filter: url(#tint-66);"
|
||||||
/>
|
/>
|
||||||
<svg
|
<svg
|
||||||
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
|
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<filter
|
<filter
|
||||||
id="tint-55"
|
id="tint-66"
|
||||||
>
|
>
|
||||||
<feflood
|
<feflood
|
||||||
flood-color="blue"
|
flood-color="blue"
|
||||||
@@ -379,7 +379,7 @@ exports[`components/Image prop "tintColor" convert to filter 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
|
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
|
||||||
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
|
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-67);"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -392,7 +392,7 @@ exports[`components/Image prop "tintColor" convert to filter 1`] = `
|
|||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<filter
|
<filter
|
||||||
id="tint-56"
|
id="tint-67"
|
||||||
>
|
>
|
||||||
<feflood
|
<feflood
|
||||||
flood-color="red"
|
flood-color="red"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Image from '../';
|
|||||||
import ImageLoader, { ImageUriCache } from '../../../modules/ImageLoader';
|
import ImageLoader, { ImageUriCache } from '../../../modules/ImageLoader';
|
||||||
import PixelRatio from '../../PixelRatio';
|
import PixelRatio from '../../PixelRatio';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act, render } from '@testing-library/react';
|
import { act, render, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
const originalImage = window.Image;
|
const originalImage = window.Image;
|
||||||
|
|
||||||
@@ -20,6 +20,16 @@ describe('components/Image', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ImageUriCache._entries = {};
|
ImageUriCache._entries = {};
|
||||||
window.Image = jest.fn(() => ({}));
|
window.Image = jest.fn(() => ({}));
|
||||||
|
ImageLoader.load = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((source, onLoad, onError) => {
|
||||||
|
act(() => onLoad({ source }));
|
||||||
|
});
|
||||||
|
ImageLoader.loadWithHeaders = jest.fn().mockImplementation((source) => ({
|
||||||
|
source,
|
||||||
|
promise: Promise.resolve(`blob:${Math.random()}`),
|
||||||
|
cancel: jest.fn()
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -102,10 +112,6 @@ describe('components/Image', () => {
|
|||||||
|
|
||||||
describe('prop "onLoad"', () => {
|
describe('prop "onLoad"', () => {
|
||||||
test('is called after image is loaded from network', () => {
|
test('is called after image is loaded from network', () => {
|
||||||
jest.useFakeTimers();
|
|
||||||
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
|
|
||||||
onLoad();
|
|
||||||
});
|
|
||||||
const onLoadStartStub = jest.fn();
|
const onLoadStartStub = jest.fn();
|
||||||
const onLoadStub = jest.fn();
|
const onLoadStub = jest.fn();
|
||||||
const onLoadEndStub = jest.fn();
|
const onLoadEndStub = jest.fn();
|
||||||
@@ -117,15 +123,10 @@ describe('components/Image', () => {
|
|||||||
source="https://test.com/img.jpg"
|
source="https://test.com/img.jpg"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
|
||||||
expect(onLoadStub).toBeCalled();
|
expect(onLoadStub).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('is called after image is loaded from cache', () => {
|
test('is called after image is loaded from cache', () => {
|
||||||
jest.useFakeTimers();
|
|
||||||
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
|
|
||||||
onLoad();
|
|
||||||
});
|
|
||||||
const onLoadStartStub = jest.fn();
|
const onLoadStartStub = jest.fn();
|
||||||
const onLoadStub = jest.fn();
|
const onLoadStub = jest.fn();
|
||||||
const onLoadEndStub = jest.fn();
|
const onLoadEndStub = jest.fn();
|
||||||
@@ -139,7 +140,6 @@ describe('components/Image', () => {
|
|||||||
source={uri}
|
source={uri}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
|
||||||
expect(onLoadStub).toBeCalled();
|
expect(onLoadStub).toBeCalled();
|
||||||
ImageUriCache.remove(uri);
|
ImageUriCache.remove(uri);
|
||||||
});
|
});
|
||||||
@@ -223,6 +223,34 @@ describe('components/Image', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('prop "onLoadStart"', () => {
|
||||||
|
test('is called on update if "headers" are modified', () => {
|
||||||
|
const onLoadStartStub = jest.fn();
|
||||||
|
const { rerender } = render(
|
||||||
|
<Image
|
||||||
|
onLoadStart={onLoadStartStub}
|
||||||
|
source={{
|
||||||
|
uri: 'https://test.com/img.jpg',
|
||||||
|
headers: { 'x-custom-header': 'abc123' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
act(() => {
|
||||||
|
rerender(
|
||||||
|
<Image
|
||||||
|
onLoadStart={onLoadStartStub}
|
||||||
|
source={{
|
||||||
|
uri: 'https://test.com/img.jpg',
|
||||||
|
headers: { 'x-custom-header': '123abc' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onLoadStartStub.mock.calls.length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('prop "resizeMode"', () => {
|
describe('prop "resizeMode"', () => {
|
||||||
['contain', 'cover', 'none', 'repeat', 'stretch', undefined].forEach(
|
['contain', 'cover', 'none', 'repeat', 'stretch', undefined].forEach(
|
||||||
(resizeMode) => {
|
(resizeMode) => {
|
||||||
@@ -241,7 +269,8 @@ describe('components/Image', () => {
|
|||||||
'',
|
'',
|
||||||
{},
|
{},
|
||||||
{ uri: '' },
|
{ uri: '' },
|
||||||
{ uri: 'https://google.com' }
|
{ uri: 'https://google.com' },
|
||||||
|
{ uri: 'https://google.com', headers: { 'x-custom-header': 'abc123' } }
|
||||||
];
|
];
|
||||||
sources.forEach((source) => {
|
sources.forEach((source) => {
|
||||||
expect(() => render(<Image source={source} />)).not.toThrow();
|
expect(() => render(<Image source={source} />)).not.toThrow();
|
||||||
@@ -257,11 +286,6 @@ describe('components/Image', () => {
|
|||||||
|
|
||||||
test('is set immediately if the image was preloaded', () => {
|
test('is set immediately if the image was preloaded', () => {
|
||||||
const uri = 'https://yahoo.com/favicon.ico';
|
const uri = 'https://yahoo.com/favicon.ico';
|
||||||
ImageLoader.load = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementationOnce((_, onLoad, onError) => {
|
|
||||||
onLoad();
|
|
||||||
});
|
|
||||||
return Image.prefetch(uri).then(() => {
|
return Image.prefetch(uri).then(() => {
|
||||||
const source = { uri };
|
const source = { uri };
|
||||||
const { container } = render(<Image source={source} />, {
|
const { container } = render(<Image source={source} />, {
|
||||||
@@ -342,6 +366,51 @@ describe('components/Image', () => {
|
|||||||
'http://localhost/static/img@2x.png'
|
'http://localhost/static/img@2x.png'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it works with headers in 2 stages', async () => {
|
||||||
|
const uri = 'https://google.com/favicon.ico';
|
||||||
|
const headers = { 'x-custom-header': 'abc123' };
|
||||||
|
const source = { uri, headers };
|
||||||
|
|
||||||
|
// Stage 1
|
||||||
|
const loadRequest = {
|
||||||
|
promise: Promise.resolve('blob:123'),
|
||||||
|
cancel: jest.fn(),
|
||||||
|
source
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageLoader.loadWithHeaders.mockReturnValue(loadRequest);
|
||||||
|
|
||||||
|
render(<Image source={source} />);
|
||||||
|
|
||||||
|
expect(ImageLoader.loadWithHeaders).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining(source)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stage 2
|
||||||
|
return waitFor(() => {
|
||||||
|
expect(ImageLoader.load).toHaveBeenCalledWith(
|
||||||
|
'blob:123',
|
||||||
|
expect.any(Function),
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// A common case is `source` declared as an inline object, which cause is to be a
|
||||||
|
// new object (with the same content) each time parent component renders
|
||||||
|
test('it still loads the image if source object is changed', () => {
|
||||||
|
const uri = 'https://google.com/favicon.ico';
|
||||||
|
const headers = { 'x-custom-header': 'abc123' };
|
||||||
|
const { rerender } = render(<Image source={{ uri, headers }} />);
|
||||||
|
rerender(<Image source={{ uri, headers }} />);
|
||||||
|
|
||||||
|
// when the underlying source didn't change we don't expect more than 1 load calls
|
||||||
|
return waitFor(() => {
|
||||||
|
expect(ImageLoader.loadWithHeaders).toHaveBeenCalledTimes(1);
|
||||||
|
expect(ImageLoader.load).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('prop "style"', () => {
|
describe('prop "style"', () => {
|
||||||
|
|||||||
+85
-19
@@ -8,6 +8,7 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ImageSource, LoadRequest } from '../../modules/ImageLoader';
|
||||||
import type { ImageProps } from './types';
|
import type { ImageProps } from './types';
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
@@ -165,6 +166,23 @@ function resolveAssetUri(source): ?string {
|
|||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function raiseOnErrorEvent(uri, { onError, onLoadEnd }) {
|
||||||
|
if (onError) {
|
||||||
|
onError({
|
||||||
|
nativeEvent: {
|
||||||
|
error: `Failed to load resource ${uri} (404)`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (onLoadEnd) onLoadEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSourceDiff(a: ImageSource, b: ImageSource) {
|
||||||
|
return (
|
||||||
|
a.uri !== b.uri || JSON.stringify(a.headers) !== JSON.stringify(b.headers)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface ImageStatics {
|
interface ImageStatics {
|
||||||
getSize: (
|
getSize: (
|
||||||
uri: string,
|
uri: string,
|
||||||
@@ -177,10 +195,12 @@ interface ImageStatics {
|
|||||||
) => Promise<{| [uri: string]: 'disk/memory' |}>;
|
) => Promise<{| [uri: string]: 'disk/memory' |}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Image: React.AbstractComponent<
|
type ImageComponent = React.AbstractComponent<
|
||||||
ImageProps,
|
ImageProps,
|
||||||
React.ElementRef<typeof View>
|
React.ElementRef<typeof View>
|
||||||
> = React.forwardRef((props, ref) => {
|
>;
|
||||||
|
|
||||||
|
const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
|
||||||
const {
|
const {
|
||||||
'aria-label': ariaLabel,
|
'aria-label': ariaLabel,
|
||||||
blurRadius,
|
blurRadius,
|
||||||
@@ -300,16 +320,7 @@ const Image: React.AbstractComponent<
|
|||||||
},
|
},
|
||||||
function error() {
|
function error() {
|
||||||
updateState(ERRORED);
|
updateState(ERRORED);
|
||||||
if (onError) {
|
raiseOnErrorEvent(uri, { onError, onLoadEnd });
|
||||||
onError({
|
|
||||||
nativeEvent: {
|
|
||||||
error: `Failed to load resource ${uri} (404)`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (onLoadEnd) {
|
|
||||||
onLoadEnd();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -353,14 +364,69 @@ const Image: React.AbstractComponent<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Image.displayName = 'Image';
|
BaseImage.displayName = 'Image';
|
||||||
|
|
||||||
// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
|
/**
|
||||||
const ImageWithStatics = (Image: React.AbstractComponent<
|
* This component handles specifically loading an image source with headers
|
||||||
ImageProps,
|
* default source is never loaded using headers
|
||||||
React.ElementRef<typeof View>
|
*/
|
||||||
> &
|
const ImageWithHeaders: ImageComponent = React.forwardRef((props, ref) => {
|
||||||
ImageStatics);
|
// $FlowIgnore: This component would only be rendered when `source` matches `ImageSource`
|
||||||
|
const nextSource: ImageSource = props.source;
|
||||||
|
const [blobUri, setBlobUri] = React.useState('');
|
||||||
|
const request = React.useRef<LoadRequest>({
|
||||||
|
cancel: () => {},
|
||||||
|
source: { uri: '', headers: {} },
|
||||||
|
promise: Promise.resolve('')
|
||||||
|
});
|
||||||
|
|
||||||
|
const { onLoadStart, ...forwardedProps } = props;
|
||||||
|
const { onError, onLoadEnd } = forwardedProps;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!hasSourceDiff(nextSource, request.current.source)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When source changes we want to clean up any old/running requests
|
||||||
|
request.current.cancel();
|
||||||
|
|
||||||
|
if (onLoadStart) {
|
||||||
|
onLoadStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a ref for the current load request so we know what's the last loaded source,
|
||||||
|
// and so we can cancel it if a different source is passed through props
|
||||||
|
request.current = ImageLoader.loadWithHeaders(nextSource);
|
||||||
|
|
||||||
|
request.current.promise
|
||||||
|
.then((uri) => setBlobUri(uri))
|
||||||
|
.catch(() =>
|
||||||
|
raiseOnErrorEvent(request.current.source.uri, { onError, onLoadEnd })
|
||||||
|
);
|
||||||
|
}, [nextSource, onLoadStart, onError, onLoadEnd]);
|
||||||
|
|
||||||
|
// Cancel any request on unmount
|
||||||
|
React.useEffect(() => request.current.cancel, []);
|
||||||
|
|
||||||
|
// Until the current component resolves the request (using headers)
|
||||||
|
// we skip forwarding the source so the base component doesn't attempt
|
||||||
|
// to load the original source
|
||||||
|
const source = blobUri ? { ...nextSource, uri: blobUri } : undefined;
|
||||||
|
|
||||||
|
return <BaseImage {...forwardedProps} ref={ref} source={source} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
const ImageWithStatics: ImageComponent & ImageStatics = React.forwardRef(
|
||||||
|
(props, ref) => {
|
||||||
|
if (props.source && props.source.headers) {
|
||||||
|
return <ImageWithHeaders {...props} ref={ref} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseImage {...props} ref={ref} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
ImageWithStatics.getSize = function (uri, success, failure) {
|
ImageWithStatics.getSize = function (uri, success, failure) {
|
||||||
ImageLoader.getSize(uri, success, failure);
|
ImageLoader.getSize(uri, success, failure);
|
||||||
|
|||||||
+55
-2
@@ -122,9 +122,18 @@ const ImageLoader = {
|
|||||||
id += 1;
|
id += 1;
|
||||||
const image = new window.Image();
|
const image = new window.Image();
|
||||||
image.onerror = onError;
|
image.onerror = onError;
|
||||||
image.onload = (e) => {
|
image.onload = (nativeEvent) => {
|
||||||
// avoid blocking the main thread
|
// avoid blocking the main thread
|
||||||
const onDecode = () => onLoad({ nativeEvent: e });
|
const onDecode = () => {
|
||||||
|
// Append `source` to match RN's ImageLoadEvent interface
|
||||||
|
nativeEvent.source = {
|
||||||
|
uri: image.src,
|
||||||
|
width: image.naturalWidth,
|
||||||
|
height: image.naturalHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad({ nativeEvent });
|
||||||
|
};
|
||||||
if (typeof image.decode === 'function') {
|
if (typeof image.decode === 'function') {
|
||||||
// Safari currently throws exceptions when decoding svgs.
|
// Safari currently throws exceptions when decoding svgs.
|
||||||
// We want to catch that error and allow the load handler
|
// We want to catch that error and allow the load handler
|
||||||
@@ -136,8 +145,41 @@ const ImageLoader = {
|
|||||||
};
|
};
|
||||||
image.src = uri;
|
image.src = uri;
|
||||||
requests[`${id}`] = image;
|
requests[`${id}`] = image;
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
|
loadWithHeaders(source: ImageSource): LoadRequest {
|
||||||
|
let uri: string;
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const request = new Request(source.uri, {
|
||||||
|
headers: source.headers,
|
||||||
|
signal: abortController.signal
|
||||||
|
});
|
||||||
|
request.headers.append('accept', 'image/*');
|
||||||
|
|
||||||
|
const promise = fetch(request)
|
||||||
|
.then((response) => response.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
uri = URL.createObjectURL(blob);
|
||||||
|
return uri;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
promise,
|
||||||
|
source,
|
||||||
|
cancel: () => {
|
||||||
|
abortController.abort();
|
||||||
|
URL.revokeObjectURL(uri);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
prefetch(uri: string): Promise<void> {
|
prefetch(uri: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ImageLoader.load(
|
ImageLoader.load(
|
||||||
@@ -164,4 +206,15 @@ const ImageLoader = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LoadRequest = {|
|
||||||
|
cancel: Function,
|
||||||
|
source: ImageSource,
|
||||||
|
promise: Promise<string>
|
||||||
|
|};
|
||||||
|
|
||||||
|
export type ImageSource = {
|
||||||
|
uri: string,
|
||||||
|
headers: { [key: string]: string }
|
||||||
|
};
|
||||||
|
|
||||||
export default ImageLoader;
|
export default ImageLoader;
|
||||||
|
|||||||
Reference in New Issue
Block a user