@plusscommunities/pluss-core-app 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module/actions/MediaActions.js +10 -1
- package/dist/module/actions/MediaActions.js.map +1 -1
- package/dist/module/actions/types.js +1 -0
- package/dist/module/actions/types.js.map +1 -1
- package/dist/module/apis/index.js +1 -0
- package/dist/module/apis/index.js.map +1 -1
- package/dist/module/apis/stringActions.js +30 -0
- package/dist/module/apis/stringActions.js.map +1 -0
- package/dist/module/components/AutoOffsetImage.js +173 -0
- package/dist/module/components/AutoOffsetImage.js.map +1 -0
- package/dist/module/components/index.js +1 -0
- package/dist/module/components/index.js.map +1 -1
- package/dist/module/helper.js +25 -2
- package/dist/module/helper.js.map +1 -1
- package/dist/module/js/images/detectFaces.js +28 -0
- package/dist/module/js/images/detectFaces.js.map +1 -0
- package/dist/module/js/images/findLandmarkRange.js +113 -0
- package/dist/module/js/images/findLandmarkRange.js.map +1 -0
- package/dist/module/js/images/getScaledOffset.js +81 -0
- package/dist/module/js/images/getScaledOffset.js.map +1 -0
- package/package.json +18 -17
- package/src/actions/MediaActions.js +8 -1
- package/src/actions/types.js +1 -0
- package/src/apis/index.js +1 -0
- package/src/apis/stringActions.js +28 -0
- package/src/components/AutoOffsetImage.js +177 -0
- package/src/components/index.js +1 -0
- package/src/helper.js +24 -0
- package/src/js/images/detectFaces.js +30 -0
- package/src/js/images/findLandmarkRange.js +105 -0
- package/src/js/images/getScaledOffset.js +83 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
import { stringActions } from '../../apis';
|
|
4
|
+
import detectFaces from './detectFaces';
|
|
5
|
+
import getScaledOffset from './getScaledOffset';
|
|
6
|
+
|
|
7
|
+
const findLandmarkRange = async (url, options) => {
|
|
8
|
+
let cachedValues;
|
|
9
|
+
|
|
10
|
+
if (options && options.cachedValues) {
|
|
11
|
+
cachedValues = options.cachedValues;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const response = await stringActions.getString('plussSpace', `imagefaces_${encodeURIComponent(url)}`);
|
|
16
|
+
cachedValues = response.data;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.log('errored from cache');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!cachedValues || !cachedValues.faces || !cachedValues.image || _.isEmpty(cachedValues.faces) && moment().add(-1, 'w').valueOf() > moment(cachedValues.timestamp).valueOf()) {
|
|
22
|
+
cachedValues = await detectFaces(url);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
faces,
|
|
27
|
+
image
|
|
28
|
+
} = cachedValues;
|
|
29
|
+
let landmarkStartX = null;
|
|
30
|
+
let landmarkStartY = null;
|
|
31
|
+
let landmarkEndX = null;
|
|
32
|
+
let landmarkEndY = null;
|
|
33
|
+
|
|
34
|
+
if (_.isEmpty(faces)) {
|
|
35
|
+
landmarkStartX = image.width / 2;
|
|
36
|
+
landmarkStartY = image.height / 2;
|
|
37
|
+
landmarkEndX = image.width / 2;
|
|
38
|
+
landmarkEndY = image.height / 2;
|
|
39
|
+
} else {
|
|
40
|
+
faces.forEach(face => {
|
|
41
|
+
const {
|
|
42
|
+
bounds
|
|
43
|
+
} = face;
|
|
44
|
+
|
|
45
|
+
if (!landmarkStartX || bounds.origin.x < landmarkStartX) {
|
|
46
|
+
landmarkStartX = bounds.origin.x;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!landmarkStartY || bounds.origin.y < landmarkStartY) {
|
|
50
|
+
landmarkStartY = bounds.origin.y;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const faceEndX = bounds.origin.x + bounds.size.width;
|
|
54
|
+
|
|
55
|
+
if (!landmarkEndX || faceEndX > landmarkEndX) {
|
|
56
|
+
landmarkEndX = faceEndX;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const faceEndY = bounds.origin.y + bounds.size.height;
|
|
60
|
+
|
|
61
|
+
if (!landmarkEndY || faceEndX > landmarkEndY) {
|
|
62
|
+
landmarkEndY = faceEndY;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let targetWidth = image.width;
|
|
68
|
+
let targetHeight = image.height;
|
|
69
|
+
|
|
70
|
+
if (options && options.targetWidth && options.targetHeight) {
|
|
71
|
+
const {
|
|
72
|
+
scaleFactor
|
|
73
|
+
} = getScaledOffset(image.width, image.height, options.targetWidth, options.targetHeight);
|
|
74
|
+
landmarkStartX *= scaleFactor;
|
|
75
|
+
landmarkStartY *= scaleFactor;
|
|
76
|
+
landmarkEndX *= scaleFactor;
|
|
77
|
+
landmarkEndY *= scaleFactor;
|
|
78
|
+
targetWidth = options.targetWidth;
|
|
79
|
+
targetHeight = options.targetHeight;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (landmarkStartX < 0) {
|
|
83
|
+
landmarkStartX = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (landmarkStartY < 0) {
|
|
87
|
+
landmarkStartY = 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (landmarkEndX > targetWidth) {
|
|
91
|
+
landmarkEndX = targetWidth;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (landmarkEndY > targetHeight) {
|
|
95
|
+
landmarkEndY = targetHeight;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
start: {
|
|
100
|
+
x: landmarkStartX,
|
|
101
|
+
y: landmarkStartY
|
|
102
|
+
},
|
|
103
|
+
end: {
|
|
104
|
+
x: landmarkEndX,
|
|
105
|
+
y: landmarkEndY
|
|
106
|
+
},
|
|
107
|
+
height: image.height,
|
|
108
|
+
width: image.width
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default findLandmarkRange;
|
|
113
|
+
//# sourceMappingURL=findLandmarkRange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["findLandmarkRange.js"],"names":["_","moment","stringActions","detectFaces","getScaledOffset","findLandmarkRange","url","options","cachedValues","response","getString","encodeURIComponent","data","e","console","log","faces","image","isEmpty","add","valueOf","timestamp","landmarkStartX","landmarkStartY","landmarkEndX","landmarkEndY","width","height","forEach","face","bounds","origin","x","y","faceEndX","size","faceEndY","targetWidth","targetHeight","scaleFactor","start","end"],"mappings":"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","sourcesContent":["import _ from 'lodash';\nimport moment from 'moment';\nimport { stringActions } from '../../apis';\nimport detectFaces from './detectFaces';\nimport getScaledOffset from './getScaledOffset';\n\nconst findLandmarkRange = async (url, options) => {\n let cachedValues;\n if (options && options.cachedValues) {\n cachedValues = options.cachedValues;\n }\n try {\n const response = await stringActions.getString('plussSpace', `imagefaces_${encodeURIComponent(url)}`);\n cachedValues = response.data;\n } catch (e) {\n console.log('errored from cache');\n }\n if (\n !cachedValues ||\n !cachedValues.faces ||\n !cachedValues.image ||\n (_.isEmpty(cachedValues.faces) &&\n moment()\n .add(-1, 'w')\n .valueOf() > moment(cachedValues.timestamp).valueOf())\n ) {\n cachedValues = await detectFaces(url);\n }\n const { faces, image } = cachedValues;\n\n let landmarkStartX = null;\n let landmarkStartY = null;\n let landmarkEndX = null;\n let landmarkEndY = null;\n\n if (_.isEmpty(faces)) {\n landmarkStartX = image.width / 2;\n landmarkStartY = image.height / 2;\n landmarkEndX = image.width / 2;\n landmarkEndY = image.height / 2;\n } else {\n faces.forEach(face => {\n const { bounds } = face;\n\n if (!landmarkStartX || bounds.origin.x < landmarkStartX) {\n landmarkStartX = bounds.origin.x;\n }\n\n if (!landmarkStartY || bounds.origin.y < landmarkStartY) {\n landmarkStartY = bounds.origin.y;\n }\n\n const faceEndX = bounds.origin.x + bounds.size.width;\n if (!landmarkEndX || faceEndX > landmarkEndX) {\n landmarkEndX = faceEndX;\n }\n\n const faceEndY = bounds.origin.y + bounds.size.height;\n if (!landmarkEndY || faceEndX > landmarkEndY) {\n landmarkEndY = faceEndY;\n }\n });\n }\n\n let targetWidth = image.width;\n let targetHeight = image.height;\n\n if (options && options.targetWidth && options.targetHeight) {\n const { scaleFactor } = getScaledOffset(image.width, image.height, options.targetWidth, options.targetHeight);\n landmarkStartX *= scaleFactor;\n landmarkStartY *= scaleFactor;\n landmarkEndX *= scaleFactor;\n landmarkEndY *= scaleFactor;\n targetWidth = options.targetWidth;\n targetHeight = options.targetHeight;\n }\n\n if (landmarkStartX < 0) {\n landmarkStartX = 0;\n }\n if (landmarkStartY < 0) {\n landmarkStartY = 0;\n }\n if (landmarkEndX > targetWidth) {\n landmarkEndX = targetWidth;\n }\n if (landmarkEndY > targetHeight) {\n landmarkEndY = targetHeight;\n }\n\n return {\n start: {\n x: landmarkStartX,\n y: landmarkStartY,\n },\n end: {\n x: landmarkEndX,\n y: landmarkEndY,\n },\n height: image.height,\n width: image.width,\n };\n};\n\nexport default findLandmarkRange;\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const getScaledOffset = (sourceWidth, sourceHeight, targetWidth, targetHeight) => {
|
|
2
|
+
const sourceRatio = sourceWidth / sourceHeight;
|
|
3
|
+
const targetRatio = targetWidth / targetHeight; // same ratio. no adjustment needed.
|
|
4
|
+
|
|
5
|
+
if (sourceRatio === targetRatio) {
|
|
6
|
+
return {
|
|
7
|
+
width: targetWidth,
|
|
8
|
+
height: targetHeight,
|
|
9
|
+
scaleFactor: targetWidth / sourceWidth,
|
|
10
|
+
offset: {
|
|
11
|
+
x: 0,
|
|
12
|
+
y: 0
|
|
13
|
+
},
|
|
14
|
+
bounds: {
|
|
15
|
+
start: {
|
|
16
|
+
x: 0,
|
|
17
|
+
y: 0
|
|
18
|
+
},
|
|
19
|
+
end: {
|
|
20
|
+
x: targetWidth,
|
|
21
|
+
y: targetHeight
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
} // source is higher than target. height scaling and y offset required.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if (sourceRatio < targetRatio) {
|
|
29
|
+
const scaleFactor = targetWidth / sourceWidth;
|
|
30
|
+
const adjustedHeight = sourceHeight * scaleFactor;
|
|
31
|
+
const yOffset = (adjustedHeight - targetHeight) / 2;
|
|
32
|
+
return {
|
|
33
|
+
width: targetWidth,
|
|
34
|
+
height: adjustedHeight,
|
|
35
|
+
scaleFactor,
|
|
36
|
+
offset: {
|
|
37
|
+
x: 0,
|
|
38
|
+
y: -yOffset
|
|
39
|
+
},
|
|
40
|
+
bounds: {
|
|
41
|
+
start: {
|
|
42
|
+
x: 0,
|
|
43
|
+
y: yOffset
|
|
44
|
+
},
|
|
45
|
+
end: {
|
|
46
|
+
x: targetWidth,
|
|
47
|
+
y: yOffset + adjustedHeight
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
} // source is wider than target. width scaling and x offset required.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if (sourceRatio > targetRatio) {
|
|
55
|
+
const scaleFactor = targetHeight / sourceHeight;
|
|
56
|
+
const adjustedWidth = sourceWidth * scaleFactor;
|
|
57
|
+
const xOffset = (adjustedWidth - targetWidth) / 2;
|
|
58
|
+
return {
|
|
59
|
+
width: adjustedWidth,
|
|
60
|
+
height: targetHeight,
|
|
61
|
+
scaleFactor,
|
|
62
|
+
offset: {
|
|
63
|
+
x: -xOffset,
|
|
64
|
+
y: 0
|
|
65
|
+
},
|
|
66
|
+
bounds: {
|
|
67
|
+
start: {
|
|
68
|
+
x: xOffset,
|
|
69
|
+
y: 0
|
|
70
|
+
},
|
|
71
|
+
end: {
|
|
72
|
+
x: xOffset + adjustedWidth,
|
|
73
|
+
y: targetHeight
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default getScaledOffset;
|
|
81
|
+
//# sourceMappingURL=getScaledOffset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["getScaledOffset.js"],"names":["getScaledOffset","sourceWidth","sourceHeight","targetWidth","targetHeight","sourceRatio","targetRatio","width","height","scaleFactor","offset","x","y","bounds","start","end","adjustedHeight","yOffset","adjustedWidth","xOffset"],"mappings":"AAAA,MAAMA,eAAe,GAAG,CAACC,WAAD,EAAcC,YAAd,EAA4BC,WAA5B,EAAyCC,YAAzC,KAA0D;AAChF,QAAMC,WAAW,GAAGJ,WAAW,GAAGC,YAAlC;AACA,QAAMI,WAAW,GAAGH,WAAW,GAAGC,YAAlC,CAFgF,CAIhF;;AACA,MAAIC,WAAW,KAAKC,WAApB,EAAiC;AAC/B,WAAO;AACLC,MAAAA,KAAK,EAAEJ,WADF;AAELK,MAAAA,MAAM,EAAEJ,YAFH;AAGLK,MAAAA,WAAW,EAAEN,WAAW,GAAGF,WAHtB;AAILS,MAAAA,MAAM,EAAE;AACNC,QAAAA,CAAC,EAAE,CADG;AAENC,QAAAA,CAAC,EAAE;AAFG,OAJH;AAQLC,MAAAA,MAAM,EAAE;AACNC,QAAAA,KAAK,EAAE;AACLH,UAAAA,CAAC,EAAE,CADE;AAELC,UAAAA,CAAC,EAAE;AAFE,SADD;AAKNG,QAAAA,GAAG,EAAE;AACHJ,UAAAA,CAAC,EAAER,WADA;AAEHS,UAAAA,CAAC,EAAER;AAFA;AALC;AARH,KAAP;AAmBD,GAzB+E,CA2BhF;;;AACA,MAAIC,WAAW,GAAGC,WAAlB,EAA+B;AAC7B,UAAMG,WAAW,GAAGN,WAAW,GAAGF,WAAlC;AACA,UAAMe,cAAc,GAAGd,YAAY,GAAGO,WAAtC;AACA,UAAMQ,OAAO,GAAG,CAACD,cAAc,GAAGZ,YAAlB,IAAkC,CAAlD;AAEA,WAAO;AACLG,MAAAA,KAAK,EAAEJ,WADF;AAELK,MAAAA,MAAM,EAAEQ,cAFH;AAGLP,MAAAA,WAHK;AAILC,MAAAA,MAAM,EAAE;AACNC,QAAAA,CAAC,EAAE,CADG;AAENC,QAAAA,CAAC,EAAE,CAACK;AAFE,OAJH;AAQLJ,MAAAA,MAAM,EAAE;AACNC,QAAAA,KAAK,EAAE;AACLH,UAAAA,CAAC,EAAE,CADE;AAELC,UAAAA,CAAC,EAAEK;AAFE,SADD;AAKNF,QAAAA,GAAG,EAAE;AACHJ,UAAAA,CAAC,EAAER,WADA;AAEHS,UAAAA,CAAC,EAAEK,OAAO,GAAGD;AAFV;AALC;AARH,KAAP;AAmBD,GApD+E,CAsDhF;;;AACA,MAAIX,WAAW,GAAGC,WAAlB,EAA+B;AAC7B,UAAMG,WAAW,GAAGL,YAAY,GAAGF,YAAnC;AACA,UAAMgB,aAAa,GAAGjB,WAAW,GAAGQ,WAApC;AACA,UAAMU,OAAO,GAAG,CAACD,aAAa,GAAGf,WAAjB,IAAgC,CAAhD;AAEA,WAAO;AACLI,MAAAA,KAAK,EAAEW,aADF;AAELV,MAAAA,MAAM,EAAEJ,YAFH;AAGLK,MAAAA,WAHK;AAILC,MAAAA,MAAM,EAAE;AACNC,QAAAA,CAAC,EAAE,CAACQ,OADE;AAENP,QAAAA,CAAC,EAAE;AAFG,OAJH;AAQLC,MAAAA,MAAM,EAAE;AACNC,QAAAA,KAAK,EAAE;AACLH,UAAAA,CAAC,EAAEQ,OADE;AAELP,UAAAA,CAAC,EAAE;AAFE,SADD;AAKNG,QAAAA,GAAG,EAAE;AACHJ,UAAAA,CAAC,EAAEQ,OAAO,GAAGD,aADV;AAEHN,UAAAA,CAAC,EAAER;AAFA;AALC;AARH,KAAP;AAmBD;AACF,CAhFD;;AAkFA,eAAeJ,eAAf","sourcesContent":["const getScaledOffset = (sourceWidth, sourceHeight, targetWidth, targetHeight) => {\n const sourceRatio = sourceWidth / sourceHeight;\n const targetRatio = targetWidth / targetHeight;\n\n // same ratio. no adjustment needed.\n if (sourceRatio === targetRatio) {\n return {\n width: targetWidth,\n height: targetHeight,\n scaleFactor: targetWidth / sourceWidth,\n offset: {\n x: 0,\n y: 0,\n },\n bounds: {\n start: {\n x: 0,\n y: 0,\n },\n end: {\n x: targetWidth,\n y: targetHeight,\n },\n },\n };\n }\n\n // source is higher than target. height scaling and y offset required.\n if (sourceRatio < targetRatio) {\n const scaleFactor = targetWidth / sourceWidth;\n const adjustedHeight = sourceHeight * scaleFactor;\n const yOffset = (adjustedHeight - targetHeight) / 2;\n\n return {\n width: targetWidth,\n height: adjustedHeight,\n scaleFactor,\n offset: {\n x: 0,\n y: -yOffset,\n },\n bounds: {\n start: {\n x: 0,\n y: yOffset,\n },\n end: {\n x: targetWidth,\n y: yOffset + adjustedHeight,\n },\n },\n };\n }\n\n // source is wider than target. width scaling and x offset required.\n if (sourceRatio > targetRatio) {\n const scaleFactor = targetHeight / sourceHeight;\n const adjustedWidth = sourceWidth * scaleFactor;\n const xOffset = (adjustedWidth - targetWidth) / 2;\n\n return {\n width: adjustedWidth,\n height: targetHeight,\n scaleFactor,\n offset: {\n x: -xOffset,\n y: 0,\n },\n bounds: {\n start: {\n x: xOffset,\n y: 0,\n },\n end: {\n x: xOffset + adjustedWidth,\n y: targetHeight,\n },\n },\n };\n }\n};\n\nexport default getScaledOffset;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plusscommunities/pluss-core-app",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Core extension package for Pluss Communities platform",
|
|
5
5
|
"main": "dist/module/index.js",
|
|
6
6
|
"module": "dist/module/index.js",
|
|
@@ -21,35 +21,36 @@
|
|
|
21
21
|
"author": "Phillip Suh",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@expo/vector-icons": "^
|
|
25
|
-
"@react-native-async-storage/async-storage": "~1.
|
|
26
|
-
"@react-native-community/netinfo": "
|
|
27
|
-
"@react-native-picker/picker": "2.
|
|
24
|
+
"@expo/vector-icons": "^13.0.0",
|
|
25
|
+
"@react-native-async-storage/async-storage": "~1.17.3",
|
|
26
|
+
"@react-native-community/netinfo": "8.2.0",
|
|
27
|
+
"@react-native-picker/picker": "2.4.0",
|
|
28
28
|
"aws-amplify": "^4.3.11",
|
|
29
29
|
"aws-amplify-react-native": "^6.0.2",
|
|
30
|
-
"expo-av": "~
|
|
31
|
-
"expo-calendar": "~10.
|
|
32
|
-
"expo-constants": "~13.
|
|
33
|
-
"expo-
|
|
34
|
-
"expo-
|
|
35
|
-
"expo-image-
|
|
36
|
-
"expo-
|
|
30
|
+
"expo-av": "~11.2.3",
|
|
31
|
+
"expo-calendar": "~10.2.0",
|
|
32
|
+
"expo-constants": "~13.1.1",
|
|
33
|
+
"expo-face-detector": "~11.2.0",
|
|
34
|
+
"expo-file-system": "~14.0.0",
|
|
35
|
+
"expo-image-manipulator": "~10.3.1",
|
|
36
|
+
"expo-image-picker": "~13.1.1",
|
|
37
|
+
"expo-linear-gradient": "~11.3.0",
|
|
37
38
|
"expo-media-library": "~14.0.0",
|
|
38
|
-
"expo-screen-orientation": "~4.
|
|
39
|
-
"expo-sharing": "~10.
|
|
39
|
+
"expo-screen-orientation": "~4.2.0",
|
|
40
|
+
"expo-sharing": "~10.2.0",
|
|
40
41
|
"expo-video-player": "^1.6.0",
|
|
41
42
|
"js-cookie": "^2.2.1",
|
|
42
43
|
"lodash": "^4.17.4",
|
|
43
44
|
"mime-types": "^2.1.24",
|
|
44
45
|
"moment": "^2.18.1",
|
|
45
|
-
"react": "17.0.
|
|
46
|
-
"react-native": "0.
|
|
46
|
+
"react": "17.0.2",
|
|
47
|
+
"react-native": "0.68.2",
|
|
47
48
|
"react-native-auto-height-image": "3.1.3",
|
|
48
49
|
"react-native-elements": "^0.17.0",
|
|
49
50
|
"react-native-image-zoom-viewer": "^3.0.1",
|
|
50
51
|
"react-native-iphone-x-helper": "^1.3.1",
|
|
51
52
|
"react-native-vimeo-iframe": "^1.0.4",
|
|
52
|
-
"react-native-webview": "11.
|
|
53
|
+
"react-native-webview": "11.18.1",
|
|
53
54
|
"react-native-youtube-iframe": "^2.2.1",
|
|
54
55
|
"react-redux": "^7.2.6"
|
|
55
56
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IMAGE_LIBRARY_LOADED, STOCK_IMAGES_LOADED, IMAGE_FOLDER_UPDATED } from './types';
|
|
1
|
+
import { IMAGE_LIBRARY_LOADED, STOCK_IMAGES_LOADED, IMAGE_FOLDER_UPDATED, SAVE_IMAGE_POSITION } from './types';
|
|
2
2
|
|
|
3
3
|
export const stockImagesLoaded = images => {
|
|
4
4
|
return {
|
|
@@ -20,3 +20,10 @@ export const imageFolderUpdated = folder => {
|
|
|
20
20
|
payload: folder,
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
+
|
|
24
|
+
export const saveImagePosition = (uri, positions) => {
|
|
25
|
+
return {
|
|
26
|
+
type: SAVE_IMAGE_POSITION,
|
|
27
|
+
payload: { uri, positions },
|
|
28
|
+
};
|
|
29
|
+
};
|
package/src/actions/types.js
CHANGED
|
@@ -3,6 +3,7 @@ export const CHANGE_ROLE = 'CHANGE_ROLE';
|
|
|
3
3
|
export const IMAGE_LIBRARY_LOADED = 'IMAGE_LIBRARY_LOADED';
|
|
4
4
|
export const STOCK_IMAGES_LOADED = 'STOCK_IMAGES_LOADED';
|
|
5
5
|
export const IMAGE_FOLDER_UPDATED = 'IMAGE_FOLDER_UPDATED';
|
|
6
|
+
export const SAVE_IMAGE_POSITION = 'SAVE_IMAGE_POSITION';
|
|
6
7
|
export const LOAD_FOLLOWERS = 'LOAD_FOLLOWERS';
|
|
7
8
|
export const ADD_FOLLOWER = 'ADD_FOLLOWER';
|
|
8
9
|
export const REMOVE_FOLLOWER = 'REMOVE_FOLLOWER';
|
package/src/apis/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from './contactActions';
|
|
|
5
5
|
export * from './eventActions';
|
|
6
6
|
export * from './analyticsActions';
|
|
7
7
|
export * from './notificationActions';
|
|
8
|
+
export * from './stringActions';
|
|
8
9
|
export * from './typeActions';
|
|
9
10
|
export { default as userActions } from './userActions';
|
|
10
11
|
export { default as profileActions } from './profileActions';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { authedFunction } from '../session';
|
|
3
|
+
import { getUrl } from '../helper';
|
|
4
|
+
|
|
5
|
+
export const stringActions = {
|
|
6
|
+
getString: (site, id, useDefault) => {
|
|
7
|
+
const url = getUrl('strings', `get/${site}_${id}`, useDefault ? { useDefault } : undefined);
|
|
8
|
+
return axios({
|
|
9
|
+
method: 'GET',
|
|
10
|
+
url,
|
|
11
|
+
}).catch(error => {
|
|
12
|
+
console.log('getString error', error);
|
|
13
|
+
throw error;
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
setString: (site, key, value) => {
|
|
17
|
+
const url = getUrl('strings', 'set');
|
|
18
|
+
return authedFunction({
|
|
19
|
+
method: 'POST',
|
|
20
|
+
url,
|
|
21
|
+
data: {
|
|
22
|
+
site,
|
|
23
|
+
key,
|
|
24
|
+
value,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import { View, Animated, StyleSheet } from 'react-native';
|
|
3
|
+
import { connect } from 'react-redux';
|
|
4
|
+
import moment from 'moment';
|
|
5
|
+
import { findLandmarkRange, getScaledOffset, get300, detectFaces } from '../helper';
|
|
6
|
+
import { saveImagePosition } from '../actions';
|
|
7
|
+
|
|
8
|
+
class AutoOffsetImage extends Component {
|
|
9
|
+
constructor(props) {
|
|
10
|
+
super(props);
|
|
11
|
+
|
|
12
|
+
this.paddingFactor = this.props.paddingFactor || 0.1;
|
|
13
|
+
|
|
14
|
+
this.state = {
|
|
15
|
+
imageStyle: {
|
|
16
|
+
width: this.props.width,
|
|
17
|
+
height: this.props.height,
|
|
18
|
+
},
|
|
19
|
+
blurRadius: new Animated.Value(100),
|
|
20
|
+
imageSource: get300(this.props.uri),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
componentDidMount() {
|
|
25
|
+
this.positionImage();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
componentDidUpdate(prevProps) {
|
|
29
|
+
if (prevProps.uri !== this.props.uri || prevProps.height !== this.props.height || prevProps.width !== this.props.width) {
|
|
30
|
+
this.positionImage();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onLoad = event => {
|
|
35
|
+
if (event.nativeEvent.source.uri === this.props.uri && !this.state.imageLoaded) {
|
|
36
|
+
//unblur
|
|
37
|
+
this.setState({
|
|
38
|
+
imageLoaded: true,
|
|
39
|
+
});
|
|
40
|
+
Animated.sequence([
|
|
41
|
+
Animated.delay(200),
|
|
42
|
+
Animated.timing(this.state.blurRadius, {
|
|
43
|
+
toValue: 0,
|
|
44
|
+
duration: 500,
|
|
45
|
+
useNativeDriver: false,
|
|
46
|
+
}),
|
|
47
|
+
]).start();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
positionImage = async () => {
|
|
52
|
+
try {
|
|
53
|
+
const startTime = moment().valueOf();
|
|
54
|
+
|
|
55
|
+
let cachedValues;
|
|
56
|
+
// check local cache
|
|
57
|
+
if (this.props.imagePositions && this.props.imagePositions[this.props.uri]) {
|
|
58
|
+
cachedValues = this.props.imagePositions[this.props.uri];
|
|
59
|
+
}
|
|
60
|
+
// check remote cache
|
|
61
|
+
if (!cachedValues) {
|
|
62
|
+
try {
|
|
63
|
+
const response = await stringActions.getString('plussSpace', `imagefaces_${encodeURIComponent(this.props.uri)}`);
|
|
64
|
+
cachedValues = response.data;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.log('errored from cache');
|
|
67
|
+
}
|
|
68
|
+
// run face detection locally
|
|
69
|
+
if (
|
|
70
|
+
!cachedValues ||
|
|
71
|
+
!cachedValues.faces ||
|
|
72
|
+
!cachedValues.image ||
|
|
73
|
+
(_.isEmpty(cachedValues.faces) &&
|
|
74
|
+
moment()
|
|
75
|
+
.add(-1, 'w')
|
|
76
|
+
.valueOf() > moment(cachedValues.timestamp).valueOf())
|
|
77
|
+
) {
|
|
78
|
+
cachedValues = await detectFaces(this.props.uri);
|
|
79
|
+
}
|
|
80
|
+
this.props.saveImagePosition(this.props.uri, cachedValues);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const image = await findLandmarkRange(this.props.uri, {
|
|
84
|
+
targetWidth: this.props.width,
|
|
85
|
+
targetHeight: this.props.height,
|
|
86
|
+
cachedValues,
|
|
87
|
+
});
|
|
88
|
+
const scaledOffset = getScaledOffset(image.width, image.height, this.props.width, this.props.height);
|
|
89
|
+
|
|
90
|
+
const { start: landmarkStart, end: landmarkEnd } = image;
|
|
91
|
+
const { bounds: imageBounds, offset, width, height } = scaledOffset;
|
|
92
|
+
|
|
93
|
+
if (landmarkStart.y < imageBounds.start.y) {
|
|
94
|
+
// move up
|
|
95
|
+
offset.y = -Math.max(landmarkStart.y - this.props.height * this.paddingFactor, 0); // move top to 5% above start of landmark
|
|
96
|
+
} else if (landmarkEnd.y > imageBounds.end.y) {
|
|
97
|
+
// move down
|
|
98
|
+
offset.y = -Math.max(landmarkEnd.y + this.props.height * this.paddingFactor - this.props.height, 0); // move bottom to 5% below end of landmark
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (landmarkStart.x < imageBounds.start.x) {
|
|
102
|
+
// move right
|
|
103
|
+
offset.x = -Math.max(landmarkStart.x - this.props.width * this.paddingFactor, 0); // move left edge to 5% left of start of landmark
|
|
104
|
+
} else if (landmarkEnd.x > imageBounds.end.x) {
|
|
105
|
+
// move left
|
|
106
|
+
offset.x = -Math.max(landmarkEnd.x + this.props.width * this.paddingFactor - this.props.width, 0); // move right edge to 5% left of end of landmark
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.setState({
|
|
110
|
+
imageStyle: {
|
|
111
|
+
width,
|
|
112
|
+
height,
|
|
113
|
+
top: offset.y,
|
|
114
|
+
left: offset.x,
|
|
115
|
+
},
|
|
116
|
+
imageSource: this.props.uri,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const endTime = moment().valueOf();
|
|
120
|
+
if (endTime - startTime < 500) {
|
|
121
|
+
Animated.timing(this.state.blurRadius, {
|
|
122
|
+
toValue: 0,
|
|
123
|
+
duration: 0,
|
|
124
|
+
useNativeDriver: false,
|
|
125
|
+
}).start();
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
this.setState({
|
|
129
|
+
imageSource: this.props.uri,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
render() {
|
|
135
|
+
return (
|
|
136
|
+
<View
|
|
137
|
+
style={[
|
|
138
|
+
styles.container,
|
|
139
|
+
{
|
|
140
|
+
width: this.props.width,
|
|
141
|
+
height: this.props.height,
|
|
142
|
+
},
|
|
143
|
+
this.props.style,
|
|
144
|
+
]}
|
|
145
|
+
>
|
|
146
|
+
<Animated.Image
|
|
147
|
+
blurRadius={this.state.blurRadius}
|
|
148
|
+
source={{ uri: this.state.imageSource }}
|
|
149
|
+
style={[styles.image, this.state.imageStyle]}
|
|
150
|
+
onLoad={this.onLoad}
|
|
151
|
+
/>
|
|
152
|
+
{this.props.children}
|
|
153
|
+
</View>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const styles = StyleSheet.create({
|
|
159
|
+
container: {
|
|
160
|
+
overflow: 'hidden',
|
|
161
|
+
},
|
|
162
|
+
image: {
|
|
163
|
+
position: 'absolute',
|
|
164
|
+
top: 0,
|
|
165
|
+
left: 0,
|
|
166
|
+
resizeMode: 'cover',
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const mapStateToProps = state => {
|
|
171
|
+
return {
|
|
172
|
+
imagePositions: state.media.positions,
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const autoOffsetImage = connect(mapStateToProps, { saveImagePosition })(AutoOffsetImage);
|
|
177
|
+
export { autoOffsetImage as AutoOffsetImage };
|
package/src/components/index.js
CHANGED
|
@@ -48,6 +48,7 @@ export { default as ImageUploadProgress } from './ImageUploadProgress';
|
|
|
48
48
|
export { default as UserListing } from './UserListing';
|
|
49
49
|
export { default as PlussChat } from './PlussChat';
|
|
50
50
|
export { default as PositionedImage } from './PositionedImage';
|
|
51
|
+
export * from './AutoOffsetImage';
|
|
51
52
|
export { default as FormattedText } from './FormattedText';
|
|
52
53
|
export { default as MediaPlayer } from './MediaPlayer';
|
|
53
54
|
export { default as AudienceSelectorPage } from './AudienceSelectorPage';
|
package/src/helper.js
CHANGED
|
@@ -5,6 +5,11 @@ import Config from './config';
|
|
|
5
5
|
import { LINEGREY } from './colours';
|
|
6
6
|
import { getSessionTokenAWS } from './session';
|
|
7
7
|
|
|
8
|
+
// seperated files to export
|
|
9
|
+
import detectFaces from './js/images/detectFaces';
|
|
10
|
+
import findLandmarkRange from './js/images/findLandmarkRange';
|
|
11
|
+
import getScaledOffset from './js/images/getScaledOffset';
|
|
12
|
+
|
|
8
13
|
const TIMESTAMP_FORMAT = 'D MMM • h:mma';
|
|
9
14
|
const DATE_FORMAT = 'D MMM YYYY';
|
|
10
15
|
|
|
@@ -155,6 +160,21 @@ const get1400 = url => {
|
|
|
155
160
|
return url;
|
|
156
161
|
};
|
|
157
162
|
|
|
163
|
+
const get300 = url => {
|
|
164
|
+
if (!url) {
|
|
165
|
+
return url;
|
|
166
|
+
}
|
|
167
|
+
if (isStockImage(url) || url.indexOf(Config.env.baseUploadsUrl) !== -1) {
|
|
168
|
+
const extension = getExtension(url);
|
|
169
|
+
let urlToUse = url;
|
|
170
|
+
if (extension !== 'jpg') {
|
|
171
|
+
urlToUse = `${url.substring(0, url.length - extension.length)}jpg`;
|
|
172
|
+
}
|
|
173
|
+
return urlToUse.replace('/uploads/', '/300/').replace('/1400/', '/300/');
|
|
174
|
+
}
|
|
175
|
+
return url;
|
|
176
|
+
};
|
|
177
|
+
|
|
158
178
|
const getMimeType = url => {
|
|
159
179
|
const extension = getExtension(url);
|
|
160
180
|
switch (extension) {
|
|
@@ -400,6 +420,7 @@ export {
|
|
|
400
420
|
getExtension,
|
|
401
421
|
getFileName,
|
|
402
422
|
isVideo,
|
|
423
|
+
get300,
|
|
403
424
|
getThumb300,
|
|
404
425
|
get1400,
|
|
405
426
|
getMimeType,
|
|
@@ -424,4 +445,7 @@ export {
|
|
|
424
445
|
getUserPreview,
|
|
425
446
|
allowComments,
|
|
426
447
|
isVideoUrl,
|
|
448
|
+
detectFaces,
|
|
449
|
+
findLandmarkRange,
|
|
450
|
+
getScaledOffset,
|
|
427
451
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { stringActions } from '../../apis';
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
import * as FileSystem from 'expo-file-system';
|
|
4
|
+
import * as FaceDetector from 'expo-face-detector';
|
|
5
|
+
|
|
6
|
+
const detectFaces = async remoteUrl => {
|
|
7
|
+
const parts = remoteUrl.split('/').splice(-2);
|
|
8
|
+
const mediaId = parts[0];
|
|
9
|
+
const extension = parts[1].split('.').splice(-1)[0];
|
|
10
|
+
|
|
11
|
+
const downloadPath = `${FileSystem.cacheDirectory}${mediaId}.${extension}`;
|
|
12
|
+
|
|
13
|
+
const { uri: localUrl } = await FileSystem.downloadAsync(remoteUrl, downloadPath);
|
|
14
|
+
|
|
15
|
+
const faces = await FaceDetector.detectFacesAsync(localUrl, {
|
|
16
|
+
mode: FaceDetector.FaceDetectorMode.accurate,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (faces && faces.image && faces.image.uri) {
|
|
20
|
+
delete faces.image.uri;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
faces.timestamp = moment().valueOf();
|
|
24
|
+
|
|
25
|
+
stringActions.setString('plussSpace', `imagefaces_${encodeURIComponent(remoteUrl)}`, faces);
|
|
26
|
+
|
|
27
|
+
return faces;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default detectFaces;
|