@tarojs/plugin-platform-harmony-ets 4.0.0-alpha.29 → 4.0.0-alpha.30

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.
@@ -9,11 +9,15 @@
9
9
  // ❌ wx.saveImageToPhotosAlbum(Object object) api 9+ HarmonyOS不支持
10
10
  // ❌ wx.previewImage(Object object) api 9+ HarmonyOS不支持
11
11
 
12
+ import fs from '@ohos.file.fs'
12
13
  import picker from '@ohos.file.picker'
13
14
  import image from '@ohos.multimedia.image'
15
+ import { Current } from '@tarojs/runtime'
14
16
  import { isNull } from '@tarojs/shared'
15
17
 
16
- import { callAsyncFail, callAsyncSuccess, temporarilyNotSupport, validateParams } from '../../utils'
18
+ import { getSystemInfoSync } from '../../base'
19
+ import { callAsyncFail, callAsyncSuccess, requestPermissions, temporarilyNotSupport, validateParams } from '../../utils'
20
+ import { IMAGE_PERMISSION } from '../../utils/permissions'
17
21
 
18
22
  import type Taro from '@tarojs/taro/types'
19
23
 
@@ -22,12 +26,21 @@ interface IPackingOptionOHOS {
22
26
  quality: number
23
27
  }
24
28
 
29
+ interface IChooseImageData {
30
+ tempFilePaths?: string[]
31
+
32
+ tempFiles?: {
33
+ path: string
34
+ size: number
35
+ }[]
36
+ }
37
+
25
38
  const getImageInfoSchema = {
26
- url: 'String'
39
+ src: 'String'
27
40
  }
28
41
 
29
42
  const compressImageSchema = {
30
- url: 'String'
43
+ src: 'String'
31
44
  }
32
45
 
33
46
  const chooseImageSchema = {
@@ -60,61 +73,211 @@ export const getImageInfo: typeof Taro.getImageInfo = function (options) {
60
73
  })
61
74
  }
62
75
 
76
+
77
+ class CompressedImageInfo {
78
+ imageUri = '' // 压缩后图片保存位置的uri
79
+ imageByteLength = 0 // 压缩后图片字节长度
80
+ }
81
+
82
+ async function saveImage(compressedImageData, compressedImageUri) {
83
+ const tempArr = compressedImageUri.split('/')
84
+ const name = tempArr[tempArr.length - 1]
85
+ const context = getContext(Current?.page)
86
+ const applicationContext = context.getApplicationContext()
87
+ const tempDir = applicationContext.tempDir
88
+ const filePath = `${tempDir}/${name}`
89
+
90
+ try {
91
+ const res = fs.accessSync(filePath)
92
+ if (res) {
93
+ // 如果图片afterCompressiona.jpeg已存在,则删除
94
+ fs.unlinkSync(filePath)
95
+ }
96
+ } catch (err) {
97
+ console.error(`[Taro] saveImage Error: AccessSync failed with error message: ${err.message}, error code: ${err.code}`)
98
+ }
99
+
100
+ // 知识点:保存图片。获取最终图片压缩数据compressedImageData,保存图片。
101
+ // 压缩图片数据写入文件
102
+ const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
103
+ fs.writeSync(file.fd, compressedImageData)
104
+ fs.closeSync(file)
105
+
106
+ // 获取压缩图片信息
107
+ const compressedImageInfo = new CompressedImageInfo()
108
+ compressedImageInfo.imageUri = filePath
109
+ compressedImageInfo.imageByteLength = compressedImageData.byteLength
110
+
111
+ return compressedImageInfo
112
+ }
113
+
63
114
  export const compressImage: typeof Taro.compressImage = function (options) {
64
115
  return new Promise((resolve, reject) => {
65
- try {
66
- validateParams('compressImage', options, compressImageSchema)
67
- } catch (error) {
68
- const res = { errMsg: error.message }
69
- return callAsyncFail(reject, res, options)
70
- }
71
- const { src, quality = 80 } = options
116
+ requestPermissions(IMAGE_PERMISSION).then(() => {
117
+ try {
118
+ validateParams('compressImage', options, compressImageSchema)
119
+ } catch (error) {
120
+ const res = { errMsg: error.message }
121
+ return callAsyncFail(reject, res, options)
122
+ }
123
+ const { src, quality = 80, compressedWidth, compressedHeight } = options
124
+ const srcAfterCompress = src.includes('_after_compress') ? src : src.split('.').join('_after_compress.')
125
+ const file = fs.openSync(src, fs.OpenMode.READ_ONLY)
72
126
 
73
- const source = image.createImageSource(src)
74
- if (isNull(source)) {
75
- const createImageSourceError = { errMsg: 'compressImage fail: createImageSource has failed.' }
76
- callAsyncFail(reject, createImageSourceError, options)
77
- return
78
- }
127
+ // const stat = fs.statSync(file.fd)
128
+ // console.log('[Taro] 压缩前图片的大小为:', stat.size)
79
129
 
80
- const packer = image.createImagePacker(src)
81
- if (isNull(packer)) {
82
- const createImagePackerError = { errMsg: 'compressImage fail: createImagePacker has failed.' }
83
- callAsyncFail(reject, createImagePackerError, options)
84
- }
130
+ const source = image.createImageSource(file.fd)
131
+ if (isNull(source)) {
132
+ const createImageSourceError = { errMsg: 'compressImage fail: createImageSource has failed.' }
133
+ callAsyncFail(reject, createImageSourceError, options)
134
+ return
135
+ }
85
136
 
86
- const packingOptionsOHOS: IPackingOptionOHOS = {
87
- // TODO:需要获取文件名后缀
88
- format: 'image/jpeg',
89
- quality: quality
90
- }
91
- packer.packing(source, packingOptionsOHOS).then((value) => {
92
- callAsyncSuccess(resolve, value, options)
93
- }).catch((error) => {
94
- callAsyncFail(reject, error, options)
137
+ const width = source.getImageInfoSync().size.width
138
+ const height = source.getImageInfoSync().size.height
139
+ let wantWidth = compressedWidth || compressedHeight || 0
140
+ let wantHeight = compressedHeight || compressedWidth || 0
141
+
142
+ if (width > wantWidth || height > wantHeight) {
143
+ const heightRatio = height / wantHeight
144
+ const widthRatio = width / wantWidth
145
+ const finalRatio = heightRatio < widthRatio ? heightRatio : widthRatio
146
+
147
+ wantWidth = Math.round(width / finalRatio)
148
+ wantHeight = Math.round(height / finalRatio)
149
+ }
150
+
151
+ const decodingOptions = {
152
+ editable: true,
153
+ desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
154
+ desiredSize: { width: wantWidth, height: wantHeight }
155
+ }
156
+ source.createPixelMap(decodingOptions, (error, pixelMap) => {
157
+ if (error !== undefined) {
158
+ fs.closeSync(file)
159
+ const res = { errMsg: error }
160
+ callAsyncFail(reject, res, options)
161
+ } else {
162
+ const packer = image.createImagePacker(file.fd)
163
+ if (isNull(packer)) {
164
+ fs.closeSync(file)
165
+ const createImagePackerError = { errMsg: 'compressImage fail: createImagePacker has failed.' }
166
+ callAsyncFail(reject, createImagePackerError, options)
167
+ return
168
+ }
169
+
170
+ const isPNG = src.endsWith('.png')
171
+ const packingOptionsOHOS: IPackingOptionOHOS = {
172
+ format: isPNG ? 'image/png' : 'image/jpeg',
173
+ quality: quality
174
+ }
175
+ packer.packing(pixelMap, packingOptionsOHOS).then((value) => {
176
+ fs.closeSync(file)
177
+ saveImage(value, srcAfterCompress).then(result => {
178
+ callAsyncSuccess(resolve, { tempFilePath: result.imageUri }, options)
179
+ })
180
+ }).catch((error) => {
181
+ fs.closeSync(file)
182
+ callAsyncFail(reject, error, options)
183
+ })
184
+ }
185
+ })
186
+ }, (error: string) => {
187
+ const res = { errMsg: error }
188
+ return callAsyncFail(reject, res, options)
95
189
  })
96
190
  })
97
191
  }
98
192
 
99
193
  export const chooseImage: typeof Taro.chooseImage = function (options) {
100
194
  return new Promise((resolve, reject) => {
101
- try {
102
- validateParams('chooseImage', options, chooseImageSchema)
103
- } catch (error) {
104
- const res = { errMsg: error.message }
105
- return callAsyncFail(reject, res, options)
106
- }
195
+ requestPermissions(IMAGE_PERMISSION).then(() => {
196
+ try {
197
+ validateParams('chooseImage', options, chooseImageSchema)
198
+ } catch (error) {
199
+ const res = { errMsg: error.message }
200
+ return callAsyncFail(reject, res, options)
201
+ }
107
202
 
108
- const { count = 9 } = options
109
- const photoViewPicker = new picker.PhotoViewPicker()
203
+ const { count = 9 } = options
204
+ const photoViewPicker = new picker.PhotoViewPicker()
205
+ let sizeType = options.sizeType
110
206
 
111
- photoSelectOptions.maxSelectNumber = count // 选择媒体文件的最大数目
112
- photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE // 过滤选择媒体文件类型为IMAGE
207
+ if (!sizeType || !sizeType.length) {
208
+ sizeType = ['compressed', 'original']
209
+ }
113
210
 
114
- photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => {
115
- callAsyncSuccess(resolve, { tempFilePaths: photoSelectResult.photoUris }, options)
116
- }).catch((error) => {
117
- callAsyncFail(reject, error, options)
211
+ photoSelectOptions.maxSelectNumber = count // 选择媒体文件的最大数目
212
+ photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE // 过滤选择媒体文件类型为IMAGE
213
+
214
+ photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => {
215
+ const result: IChooseImageData = {}
216
+ const isOrigin = photoSelectResult.isOriginalPhoto
217
+
218
+ if (isOrigin) {
219
+ const tempFilePaths: string[] = []
220
+ const tempFiles = photoSelectResult.photoUris.map(uri => {
221
+ const file = fs.openSync(uri, fs.OpenMode.READ_ONLY)
222
+ const stat = fs.statSync(file.fd)
223
+ const size = stat.size
224
+
225
+ fs.closeSync(file)
226
+ tempFilePaths.push(uri)
227
+
228
+ return {
229
+ size,
230
+ path: uri,
231
+ }
232
+ })
233
+
234
+ result.tempFiles = tempFiles
235
+ result.tempFilePaths = tempFilePaths
236
+
237
+ callAsyncSuccess(resolve, result, options)
238
+ } else {
239
+ const actions: Promise<string>[] = photoSelectResult.photoUris.map(uri => {
240
+ return new Promise<string>(resolve => {
241
+ compressImage({
242
+ src: uri,
243
+ compressedWidth: getSystemInfoSync().screenWidth / 2,
244
+ compressedHeight: getSystemInfoSync().screenHeight / 2,
245
+ success: (compressResult) => {
246
+ resolve(compressResult.tempFilePath)
247
+ }
248
+ })
249
+ })
250
+ })
251
+
252
+ Promise.all(actions).then(tempFilePaths => {
253
+ const tempFiles = tempFilePaths.map(uri => {
254
+ const file = fs.openSync(uri, fs.OpenMode.READ_ONLY)
255
+ const stat = fs.statSync(file.fd)
256
+ const size: number = stat.size
257
+
258
+ fs.closeSync(file)
259
+
260
+ return {
261
+ size,
262
+ path: uri,
263
+ }
264
+ })
265
+
266
+ result.tempFilePaths = tempFilePaths
267
+ result.tempFiles = tempFiles
268
+
269
+ callAsyncSuccess(resolve, result, options)
270
+ }).catch(error => {
271
+ const res = { errMsg: error }
272
+ return callAsyncFail(reject, res, options)
273
+ })
274
+ }
275
+ }).catch((error) => {
276
+ callAsyncFail(reject, error, options)
277
+ })
278
+ }, (error: string) => {
279
+ const res = { errMsg: error }
280
+ return callAsyncFail(reject, res, options)
118
281
  })
119
282
  })
120
283
  }
@@ -1,4 +1,5 @@
1
- import { eventCenter } from '@tarojs/runtime/dist/runtime.esm'
1
+ import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
2
+ import { Current, eventCenter } from '@tarojs/runtime'
2
3
 
3
4
  import { ICallbackResult, MethodHandler } from './handler'
4
5
 
@@ -8,6 +9,22 @@ export * from './validate'
8
9
  export { MethodHandler }
9
10
  export { noop } from '@tarojs/shared'
10
11
 
12
+ export function requestPermissions (permissions: string[]) {
13
+ return new Promise<void>((resolve, reject) => {
14
+ const context = getContext(Current?.page)
15
+ const atManager = abilityAccessCtrl.createAtManager()
16
+
17
+ atManager.requestPermissionsFromUser(context, permissions, (err, _) => {
18
+ if (err) {
19
+ // eslint-disable-next-line prefer-promise-reject-errors
20
+ reject(`[Taro] 请求用户授权 ${permissions.join('、')} 失败:${JSON.stringify(err)}`)
21
+ } else {
22
+ resolve()
23
+ }
24
+ })
25
+ })
26
+ }
27
+
11
28
  export function object2String (obj) {
12
29
  let str = ''
13
30
  for (const item in obj) {
@@ -0,0 +1,6 @@
1
+ export const READ_IMAGEVIDEO_PERMISSIONS = 'ohos.permission.READ_IMAGEVIDEO'
2
+ export const READ_MEDIA_PERMISSIONS = 'ohos.permission.READ_MEDIA'
3
+ export const WRITE_MEDIA_PERMISSIONS = 'ohos.permission.WRITE_MEDIA'
4
+ export const MEDIA_LOCATION_PERMISSIONS = 'ohos.permission.MEDIA_LOCATION'
5
+
6
+ export const IMAGE_PERMISSION = [READ_IMAGEVIDEO_PERMISSIONS, READ_MEDIA_PERMISSIONS, WRITE_MEDIA_PERMISSIONS, MEDIA_LOCATION_PERMISSIONS]
@@ -222,6 +222,7 @@ export function setSpecialTextAttributeIntoInstance(instance: TextAttribute, sty
222
222
  }
223
223
  if (!isUndefined(style.WebkitLineClamp)) {
224
224
  instance.maxLines(style.WebkitLineClamp)
225
+ instance.textOverflow({overflow: TextOverflow.Ellipsis})
225
226
  }
226
227
  if (!isUndefined(style.letterSpacing)) {
227
228
  instance.letterSpacing(style.letterSpacing)
@@ -368,16 +369,20 @@ export function setNormalAttributeIntoInstance(instance: CommonAttribute, style:
368
369
  }
369
370
  if (style.position === 'absolute' || style.position === 'fixed') {
370
371
  instance.position({
371
- x: style.left || 0,
372
- y: style.top || 0,
372
+ left: style.left,
373
+ top: style.top,
374
+ right: style.right,
375
+ bottom: style.bottom,
373
376
  })
374
377
  // 绝对定位和固定定位在web上都会脱离文档流,因此需要设置zIndex让它相比正常流的元素更上层
375
378
  instance.zIndex(1)
376
379
  }
377
380
  if (style.position === 'relative') {
378
381
  instance.offset({
379
- x: style.left || 0,
380
- y: style.top || 0,
382
+ left: style.left,
383
+ top: style.top,
384
+ right: style.right,
385
+ bottom: style.bottom,
381
386
  })
382
387
  // 绝对定位和固定定位在web上都会脱离文档流,因此需要设置zIndex让它相比正常流的元素更上层
383
388
  instance.zIndex(1)
@@ -89,6 +89,8 @@ export function isMaxWidthView (node: TaroElement) {
89
89
  }
90
90
 
91
91
  export function getNormalAttributes (node: TaroElement, initStyle?: HarmonyStyle): HarmonyStyle {
92
+ if (!node) return {}
93
+
92
94
  const hmStyle = node.hmStyle
93
95
 
94
96
  if (!hmStyle) return {}
@@ -34,7 +34,26 @@ export class TaroElement<
34
34
  layer: 0 // 渲染层级
35
35
  }
36
36
 
37
- public hm_instance: TaroAny
37
+ public _hm_instance: TaroAny
38
+ public weak_hm_instance: WeakRef<TaroAny>
39
+ public use_weak_hm_instance: boolean = true
40
+
41
+
42
+ public get hm_instance(): TaroAny {
43
+ if (this.use_weak_hm_instance && this.weak_hm_instance) {
44
+ return this.weak_hm_instance?.deref()
45
+ }
46
+ return this._hm_instance
47
+ }
48
+
49
+ public set hm_instance(instance) {
50
+ if (this.use_weak_hm_instance && instance) {
51
+ this.weak_hm_instance = new WeakRef(instance)
52
+ return
53
+ }
54
+ this._hm_instance = instance
55
+ }
56
+
38
57
 
39
58
  public get _instance () {
40
59
  return this.hm_instance
@@ -594,9 +594,9 @@ function setBackgroundPosistion (hmStyle, value) {
594
594
  } else if (horizontal === 'right' && vertical === 'bottom') {
595
595
  hmStyle.backgroundPosition = Alignment.BottomEnd
596
596
  } else {
597
- if (/^\d+(\.\d+)?(px|%|vw|vh)$/.test(horizontal)) {
597
+ if (/^-?\d+(\.\d+)?(px|%|vw|vh)$/.test(horizontal)) {
598
598
  hmStyle.backgroundPosition = { x: getUnit(horizontal) }
599
- if (/^\d+(\.\d+)?(px|%|vw|vh)$/.test(vertical)) {
599
+ if (/^-?\d+(\.\d+)?(px|%|vw|vh)$/.test(vertical)) {
600
600
  hmStyle.backgroundPosition = { x: getUnit(horizontal), y: getUnit(vertical) }
601
601
  }
602
602
  }
@@ -38,10 +38,10 @@ export interface TaroStyleType {
38
38
 
39
39
  // position
40
40
  position?: 'relative' | 'absolute' | 'fixed'
41
- top?: Length
42
- left?: Length
43
- bottom?: Length
44
- right?: Length
41
+ top?: Dimension
42
+ left?: Dimension
43
+ bottom?: Dimension
44
+ right?: Dimension
45
45
 
46
46
  // flex
47
47
  flexBasis?: number | string
@@ -133,27 +133,33 @@ function initNativeComponentEntry (params: InitNativeComponentEntryParams) {
133
133
  },
134
134
  }),
135
135
  }
136
- this.setState(
137
- {
138
- components: [...this.state.components, item],
139
- },
140
- () => cb && cb()
141
- )
136
+
137
+ ReactDOM.flushSync(() => {
138
+ this.setState(
139
+ {
140
+ components: [...this.state.components, item],
141
+ },
142
+ () => cb && cb()
143
+ )
144
+ })
142
145
  }
143
146
 
144
147
  unmount (compId, cb?) {
145
148
  const components = this.state.components
146
149
  const index = components.findIndex((item) => item.compId === compId)
147
150
  const next = [...components.slice(0, index), ...components.slice(index + 1)]
148
- this.setState(
149
- {
150
- components: next,
151
- },
152
- () => {
153
- removePageInstance(compId)
154
- cb && cb()
155
- }
156
- )
151
+
152
+ ReactDOM.flushSync(() => {
153
+ this.setState(
154
+ {
155
+ components: next,
156
+ },
157
+ () => {
158
+ removePageInstance(compId)
159
+ cb && cb()
160
+ }
161
+ )
162
+ })
157
163
  }
158
164
 
159
165
  render () {
@@ -175,7 +181,10 @@ function initNativeComponentEntry (params: InitNativeComponentEntryParams) {
175
181
  }
176
182
 
177
183
  const root = ReactDOM.createRoot(app)
178
- root.render?.(h(Entry))
184
+
185
+ ReactDOM.flushSync(() => {
186
+ root.render?.(h(Entry))
187
+ })
179
188
  }
180
189
 
181
190