@neko-os/ui 0.5.3 → 0.6.0
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/NekoUI.js +12 -9
- package/dist/abstractions/Image.native.js +1 -1
- package/dist/abstractions/Image.web.js +1 -1
- package/dist/abstractions/WindowOverlay.js +3 -0
- package/dist/abstractions/WindowOverlay.native.js +21 -0
- package/dist/abstractions/helpers/storage.js +14 -4
- package/dist/abstractions/helpers/storage.native.js +9 -1
- package/dist/components/feedback/notifications/NotificationsHandler.js +10 -6
- package/dist/components/form/useNewForm.js +2 -0
- package/dist/components/index.js +3 -1
- package/dist/components/inputs/DateInput.js +10 -6
- package/dist/components/inputs/InputWrapper.js +11 -11
- package/dist/components/inputs/NumberWheelInput.js +50 -0
- package/dist/components/inputs/NumberWheelPicker.js +43 -0
- package/dist/components/inputs/SegmentedPicker.js +3 -2
- package/dist/components/inputs/UploadInput.js +4 -4
- package/dist/components/inputs/WheelPicker.js +49 -0
- package/dist/components/inputs/WheelPicker.native.js +88 -0
- package/dist/components/inputs/WheelPicker.web.js +1 -0
- package/dist/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
- package/dist/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
- package/dist/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
- package/dist/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
- package/dist/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
- package/dist/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
- package/dist/components/inputs/index.js +5 -1
- package/dist/components/inputs/upload/Upload.native.js +60 -52
- package/dist/components/inputs/upload/useUploadState.js +11 -3
- package/dist/components/measurements/FeetInchesInput.js +91 -0
- package/dist/components/measurements/LengthInput.js +32 -0
- package/dist/components/measurements/LengthText.js +10 -0
- package/dist/components/measurements/MeasurementHandler.js +26 -0
- package/dist/components/measurements/WeightInput.js +25 -0
- package/dist/components/measurements/WeightText.js +10 -0
- package/dist/components/measurements/helpers/detectMeasurementSystem.js +15 -0
- package/dist/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
- package/dist/components/measurements/helpers/index.js +2 -0
- package/dist/components/measurements/helpers/length.js +112 -0
- package/dist/components/measurements/helpers/weight.js +56 -0
- package/dist/components/measurements/index.js +9 -0
- package/dist/components/measurements/useLengthFormatter.js +35 -0
- package/dist/components/measurements/useLocalInputValue.js +32 -0
- package/dist/components/measurements/useWeightFormatter.js +29 -0
- package/dist/components/presentation/Avatar.js +3 -3
- package/dist/components/routing/ReturnButton.js +20 -0
- package/dist/components/routing/ReturnButton.native.js +20 -0
- package/dist/components/routing/ReturnButton.web.js +2 -0
- package/dist/components/routing/ReturnLink.js +25 -0
- package/dist/components/routing/ReturnLink.native.js +25 -0
- package/dist/components/routing/ReturnLink.web.js +2 -0
- package/dist/components/routing/RoutedStepsContent.js +21 -0
- package/dist/components/routing/RoutedStepsContent.native.js +94 -0
- package/dist/components/routing/RoutedStepsContent.web.js +3 -0
- package/dist/components/routing/index.js +3 -0
- package/dist/components/state/StatePresenter.js +1 -1
- package/dist/components/steps/StepsHandler.js +2 -0
- package/dist/components/structure/TopBar.js +18 -16
- package/dist/components/theme/ThemePickerDrawer.js +1 -1
- package/dist/helpers/compress.js +61 -0
- package/dist/helpers/compress.native.js +49 -0
- package/dist/helpers/files.js +7 -0
- package/dist/helpers/files.native.js +55 -0
- package/dist/helpers/index.js +6 -1
- package/dist/helpers/media.js +4 -0
- package/dist/helpers/media.native.js +41 -0
- package/dist/helpers/numbers.js +13 -0
- package/dist/helpers/pickAssets.js +7 -0
- package/dist/helpers/pickAssets.native.js +66 -0
- package/dist/helpers/storage.js +17 -0
- package/dist/i18n/I18n.js +4 -4
- package/dist/index.js +1 -1
- package/dist/modifiers/flex.js +8 -3
- package/dist/responsive/responsiveHooks.js +14 -0
- package/dist/theme/default/blackTheme.js +3 -1
- package/dist/theme/default/cyberpunkTheme.js +3 -1
- package/dist/theme/default/darkTheme.js +3 -1
- package/dist/theme/default/hackerTheme.js +3 -1
- package/dist/theme/default/lightTheme.js +3 -1
- package/dist/theme/default/paperTheme.js +3 -1
- package/package.json +2 -14
- package/src/NekoUI.js +16 -13
- package/src/abstractions/Image.native.js +1 -1
- package/src/abstractions/Image.web.js +1 -1
- package/src/abstractions/WindowOverlay.js +3 -0
- package/src/abstractions/WindowOverlay.native.js +21 -0
- package/src/abstractions/helpers/storage.js +13 -3
- package/src/abstractions/helpers/storage.native.js +8 -0
- package/src/components/feedback/notifications/NotificationsHandler.js +12 -8
- package/src/components/form/useNewForm.js +2 -0
- package/src/components/index.js +2 -0
- package/src/components/inputs/DateInput.js +8 -4
- package/src/components/inputs/InputWrapper.js +3 -3
- package/src/components/inputs/NumberWheelInput.js +50 -0
- package/src/components/inputs/NumberWheelPicker.js +43 -0
- package/src/components/inputs/SegmentedPicker.js +2 -1
- package/src/components/inputs/UploadInput.js +2 -2
- package/src/components/inputs/WheelPicker.js +49 -0
- package/src/components/inputs/WheelPicker.native.js +88 -0
- package/src/components/inputs/WheelPicker.web.js +1 -0
- package/src/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
- package/src/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
- package/src/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
- package/src/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
- package/src/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
- package/src/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
- package/src/components/inputs/index.js +4 -0
- package/src/components/inputs/upload/Upload.native.js +58 -50
- package/src/components/inputs/upload/useUploadState.js +11 -3
- package/src/components/measurements/FeetInchesInput.js +91 -0
- package/src/components/measurements/LengthInput.js +32 -0
- package/src/components/measurements/LengthText.js +10 -0
- package/src/components/measurements/MeasurementHandler.js +26 -0
- package/src/components/measurements/WeightInput.js +25 -0
- package/src/components/measurements/WeightText.js +10 -0
- package/src/components/measurements/helpers/detectMeasurementSystem.js +15 -0
- package/src/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
- package/src/components/measurements/helpers/index.js +2 -0
- package/src/components/measurements/helpers/length.js +112 -0
- package/src/components/measurements/helpers/weight.js +56 -0
- package/src/components/measurements/index.js +9 -0
- package/src/components/measurements/useLengthFormatter.js +35 -0
- package/src/components/measurements/useLocalInputValue.js +32 -0
- package/src/components/measurements/useWeightFormatter.js +29 -0
- package/src/components/presentation/Avatar.js +2 -2
- package/src/components/routing/ReturnButton.js +20 -0
- package/src/components/routing/ReturnButton.native.js +20 -0
- package/src/components/routing/ReturnButton.web.js +2 -0
- package/src/components/routing/ReturnLink.js +25 -0
- package/src/components/routing/ReturnLink.native.js +25 -0
- package/src/components/routing/ReturnLink.web.js +2 -0
- package/src/components/routing/RoutedStepsContent.js +21 -0
- package/src/components/routing/RoutedStepsContent.native.js +94 -0
- package/src/components/routing/RoutedStepsContent.web.js +3 -0
- package/src/components/routing/index.js +3 -0
- package/src/components/state/StatePresenter.js +1 -1
- package/src/components/steps/StepsHandler.js +2 -0
- package/src/components/structure/TopBar.js +16 -14
- package/src/components/theme/ThemePickerDrawer.js +1 -1
- package/src/helpers/compress.js +61 -0
- package/src/helpers/compress.native.js +49 -0
- package/src/helpers/files.js +7 -0
- package/src/helpers/files.native.js +55 -0
- package/src/helpers/index.js +6 -1
- package/src/helpers/media.js +4 -0
- package/src/helpers/media.native.js +41 -0
- package/src/helpers/numbers.js +13 -0
- package/src/helpers/pickAssets.js +7 -0
- package/src/helpers/pickAssets.native.js +66 -0
- package/src/helpers/storage.js +17 -0
- package/src/i18n/I18n.js +2 -2
- package/src/index.js +1 -1
- package/src/modifiers/flex.js +7 -2
- package/src/responsive/responsiveHooks.js +14 -0
- package/src/theme/default/blackTheme.js +2 -0
- package/src/theme/default/cyberpunkTheme.js +2 -0
- package/src/theme/default/darkTheme.js +2 -0
- package/src/theme/default/hackerTheme.js +2 -0
- package/src/theme/default/lightTheme.js +2 -0
- package/src/theme/default/paperTheme.js +2 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const IMAGE_DEFAULTS = { maxWidth: 1920, maxHeight: 1920, quality: 0.8 }
|
|
2
|
+
|
|
3
|
+
function isImage(asset) {
|
|
4
|
+
return asset?.type?.startsWith('image/')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function loadImage(src) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const img = new Image()
|
|
10
|
+
img.onload = () => resolve(img)
|
|
11
|
+
img.onerror = reject
|
|
12
|
+
img.src = src
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ALPHA_TYPES = new Set(['image/png', 'image/webp', 'image/gif'])
|
|
17
|
+
|
|
18
|
+
function compressWithCanvas(img, { maxWidth, maxHeight, quality, mimeType }) {
|
|
19
|
+
let { width, height } = img
|
|
20
|
+
if (width > maxWidth || height > maxHeight) {
|
|
21
|
+
const ratio = Math.min(maxWidth / width, maxHeight / height)
|
|
22
|
+
width = Math.round(width * ratio)
|
|
23
|
+
height = Math.round(height * ratio)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const canvas = document.createElement('canvas')
|
|
27
|
+
canvas.width = width
|
|
28
|
+
canvas.height = height
|
|
29
|
+
const ctx = canvas.getContext('2d')
|
|
30
|
+
ctx.drawImage(img, 0, 0, width, height)
|
|
31
|
+
|
|
32
|
+
const outputType = ALPHA_TYPES.has(mimeType) ? mimeType : 'image/jpeg'
|
|
33
|
+
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
canvas.toBlob(
|
|
36
|
+
(blob) => resolve(blob ? { uri: URL.createObjectURL(blob), width, height } : null),
|
|
37
|
+
outputType,
|
|
38
|
+
quality
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function compressAsset(asset, options = {}) {
|
|
44
|
+
if (!asset?.uri || !isImage(asset)) return asset
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const opts = { ...IMAGE_DEFAULTS, ...options.image }
|
|
48
|
+
const img = await loadImage(asset.uri)
|
|
49
|
+
const result = await compressWithCanvas(img, { ...opts, mimeType: asset.type })
|
|
50
|
+
if (!result) return asset
|
|
51
|
+
return { ...asset, uri: result.uri, width: result.width, height: result.height }
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.warn('[neko-ui compress] web image compression failed:', e?.message)
|
|
54
|
+
return asset
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function compressAssets(assets, options = {}) {
|
|
59
|
+
if (!assets?.length) return Promise.resolve(assets || [])
|
|
60
|
+
return Promise.all(assets.map((a) => compressAsset(a, options)))
|
|
61
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
let ImageCompressor
|
|
2
|
+
let VideoCompressor
|
|
3
|
+
try {
|
|
4
|
+
const RNC = require('react-native-compressor')
|
|
5
|
+
ImageCompressor = RNC.Image
|
|
6
|
+
VideoCompressor = RNC.Video
|
|
7
|
+
} catch {}
|
|
8
|
+
|
|
9
|
+
const IMAGE_DEFAULTS = { maxWidth: 1920, maxHeight: 1920, quality: 0.8 }
|
|
10
|
+
const VIDEO_DEFAULTS = { maxSize: 720 }
|
|
11
|
+
|
|
12
|
+
function isImage(asset) {
|
|
13
|
+
return asset?.type?.startsWith('image/')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isVideo(asset) {
|
|
17
|
+
return asset?.type?.startsWith('video/')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function compressAsset(asset, options = {}) {
|
|
21
|
+
if (!asset?.uri) return asset
|
|
22
|
+
|
|
23
|
+
if (isImage(asset) && ImageCompressor) {
|
|
24
|
+
try {
|
|
25
|
+
const uri = await ImageCompressor.compress(asset.uri, { ...IMAGE_DEFAULTS, ...options.image })
|
|
26
|
+
return { ...asset, uri }
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.warn('[neko-ui compress] image failed, keeping original:', e?.message)
|
|
29
|
+
return asset
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (isVideo(asset) && VideoCompressor) {
|
|
34
|
+
try {
|
|
35
|
+
const uri = await VideoCompressor.compress(asset.uri, { ...VIDEO_DEFAULTS, ...options.video })
|
|
36
|
+
return { ...asset, uri }
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn('[neko-ui compress] video failed, keeping original:', e?.message)
|
|
39
|
+
return asset
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return asset
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function compressAssets(assets, options = {}) {
|
|
47
|
+
if (!assets?.length) return Promise.resolve(assets || [])
|
|
48
|
+
return Promise.all(assets.map((a) => compressAsset(a, options)))
|
|
49
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
let FS
|
|
2
|
+
try {
|
|
3
|
+
FS = require('expo-file-system')
|
|
4
|
+
} catch {}
|
|
5
|
+
|
|
6
|
+
// `target="<base>/<subdir>"`, base is 'document' | 'cache'. Resolves to a
|
|
7
|
+
// permanent Directory (created if missing) via the modern sync expo-file-system API.
|
|
8
|
+
function resolveDir(target) {
|
|
9
|
+
const [base, ...rest] = (target || '').split('/').filter(Boolean)
|
|
10
|
+
if (base !== 'cache' && base !== 'document') {
|
|
11
|
+
console.warn(`[neko-ui files] target base "${base}" unknown — using document dir. Use "document" or "cache".`)
|
|
12
|
+
}
|
|
13
|
+
const root = base === 'cache' ? FS.Paths.cache : FS.Paths.document
|
|
14
|
+
const dir = new FS.Directory(root, ...rest)
|
|
15
|
+
try {
|
|
16
|
+
dir.create({ intermediates: true, idempotent: true })
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.warn('[neko-ui files] dir create failed:', e?.message)
|
|
19
|
+
}
|
|
20
|
+
return dir
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let seq = 0
|
|
24
|
+
|
|
25
|
+
// Copy a file at `uri` into a permanent app directory (`target`, default
|
|
26
|
+
// 'document/files'). Returns the durable uri string. Never throws — on failure
|
|
27
|
+
// or when expo-file-system is unavailable, returns the original uri.
|
|
28
|
+
// `opts.name` is used only to derive the file extension (falls back to the uri,
|
|
29
|
+
// then 'jpg'); the stored filename is always unique (timestamp + counter), so
|
|
30
|
+
// rapid multi-select can't collide. Expects a `file://` source (e.g.
|
|
31
|
+
// expo-image-picker output); `content://` sources can't be copied and fall back
|
|
32
|
+
// to the original uri.
|
|
33
|
+
export function persistFile(uri, target = 'document/files', { name } = {}) {
|
|
34
|
+
if (!FS || !uri) return uri
|
|
35
|
+
try {
|
|
36
|
+
const ext = name?.split('.').pop() || uri.split('?')[0].split('.').pop() || 'jpg'
|
|
37
|
+
const filename = `${Date.now()}_${seq++}_${Math.round(Math.random() * 1e6)}.${ext}`
|
|
38
|
+
const dest = new FS.File(resolveDir(target), filename)
|
|
39
|
+
new FS.File(uri).copy(dest)
|
|
40
|
+
return dest.uri
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.warn('[neko-ui files] persistFile failed, keeping uri:', e?.message)
|
|
43
|
+
return uri
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Delete a persisted file. No-op if missing / unavailable.
|
|
48
|
+
export function removeFile(uri) {
|
|
49
|
+
if (!FS || !uri) return
|
|
50
|
+
try {
|
|
51
|
+
new FS.File(uri).delete()
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.warn('[neko-ui files] removeFile failed:', e?.message)
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/helpers/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export * from './compress'
|
|
1
2
|
export * from './debounce'
|
|
2
|
-
export * from './
|
|
3
|
+
export * from './files'
|
|
4
|
+
export * from './media'
|
|
5
|
+
export * from './numbers'
|
|
6
|
+
export * from './pickAssets'
|
|
3
7
|
export * from './random'
|
|
4
8
|
export * from './storage'
|
|
9
|
+
export * from './string'
|
|
5
10
|
export * from './weekStart'
|
|
6
11
|
export * from './weekStartSetup'
|
|
7
12
|
export * from './../abstractions/helpers/useSafeAreaInsets'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
let ImagePicker
|
|
2
|
+
try {
|
|
3
|
+
ImagePicker = require('expo-image-picker')
|
|
4
|
+
} catch {}
|
|
5
|
+
|
|
6
|
+
// Normalize an expo-image-picker asset into the shape neko-ui works with.
|
|
7
|
+
// Internal — callers consume the already-normalized assets from openCamera/openLibrary.
|
|
8
|
+
function normalizeImageResult(asset) {
|
|
9
|
+
return {
|
|
10
|
+
uri: asset.uri,
|
|
11
|
+
name: asset.fileName || asset.uri.split('/').pop(),
|
|
12
|
+
type: asset.mimeType || asset.type || 'image/jpeg',
|
|
13
|
+
size: asset.fileSize || asset.filesize,
|
|
14
|
+
width: asset.width,
|
|
15
|
+
height: asset.height,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Request camera permission, launch the camera, return normalized assets.
|
|
20
|
+
// Returns [] when expo-image-picker is missing, permission is denied, or the user
|
|
21
|
+
// cancels. The caller owns post-processing (persist, save-to-library, closing any
|
|
22
|
+
// drawer) — and any drawer should stay open until this resolves (iOS present/
|
|
23
|
+
// dismiss collision otherwise).
|
|
24
|
+
export async function openCamera(options = {}) {
|
|
25
|
+
if (!ImagePicker) return []
|
|
26
|
+
const permission = await ImagePicker.requestCameraPermissionsAsync()
|
|
27
|
+
if (!permission.granted) return []
|
|
28
|
+
const result = await ImagePicker.launchCameraAsync(options)
|
|
29
|
+
if (result.canceled) return []
|
|
30
|
+
return result.assets.map(normalizeImageResult)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Same as openCamera but for the photo library.
|
|
34
|
+
export async function openLibrary(options = {}) {
|
|
35
|
+
if (!ImagePicker) return []
|
|
36
|
+
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync()
|
|
37
|
+
if (!permission.granted) return []
|
|
38
|
+
const result = await ImagePicker.launchImageLibraryAsync(options)
|
|
39
|
+
if (result.canceled) return []
|
|
40
|
+
return result.assets.map(normalizeImageResult)
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { is } from 'ramda'
|
|
2
|
+
|
|
3
|
+
export function fixedDecimals(num, count = 2) {
|
|
4
|
+
if (!num) return num
|
|
5
|
+
if (Number.isInteger(num)) return num
|
|
6
|
+
if (is(String, num)) num = parseFloat(num)
|
|
7
|
+
|
|
8
|
+
const decimalPart = num.toString().split('.')[1]
|
|
9
|
+
if (decimalPart && decimalPart.length > count) {
|
|
10
|
+
return parseFloat(num.toFixed(count))
|
|
11
|
+
}
|
|
12
|
+
return num
|
|
13
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { openCamera, openLibrary } from './media'
|
|
2
|
+
import { compressAsset, compressAssets } from './compress'
|
|
3
|
+
import { persistFile } from './files'
|
|
4
|
+
|
|
5
|
+
let MediaLibrary
|
|
6
|
+
try {
|
|
7
|
+
MediaLibrary = require('expo-media-library')
|
|
8
|
+
} catch {}
|
|
9
|
+
|
|
10
|
+
async function saveAssetsToLibrary(assets) {
|
|
11
|
+
if (!MediaLibrary || !assets?.length) return
|
|
12
|
+
try {
|
|
13
|
+
const perm = await MediaLibrary.requestPermissionsAsync(true)
|
|
14
|
+
if (!perm.granted) return
|
|
15
|
+
for (const a of assets) {
|
|
16
|
+
try {
|
|
17
|
+
await MediaLibrary.saveToLibraryAsync(a.uri)
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.warn('[neko-ui pick] saveToLibrary failed:', e?.message)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.warn('[neko-ui pick] saveToLibrary permission error:', e?.message)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function persistAsset(asset, persistTo) {
|
|
28
|
+
if (!persistTo) return asset
|
|
29
|
+
return { ...asset, uri: persistFile(asset.uri, persistTo, { name: asset.name }) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function processAssets(assets, { compress = true, persistTo } = {}) {
|
|
33
|
+
let result = assets
|
|
34
|
+
if (compress !== false) {
|
|
35
|
+
const compressOpts = typeof compress === 'object' ? compress : {}
|
|
36
|
+
result = await compressAssets(result, compressOpts)
|
|
37
|
+
}
|
|
38
|
+
if (persistTo) {
|
|
39
|
+
result = result.map((a) => persistAsset(a, persistTo))
|
|
40
|
+
}
|
|
41
|
+
return result
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function pickFromCamera({ multiple = false, maxCount, mediaTypes, persistTo, compress, saveToLibrary } = {}) {
|
|
45
|
+
const assets = await openCamera({
|
|
46
|
+
allowsMultipleSelection: multiple,
|
|
47
|
+
selectionLimit: maxCount || 0,
|
|
48
|
+
mediaTypes,
|
|
49
|
+
})
|
|
50
|
+
if (!assets.length) return multiple ? [] : null
|
|
51
|
+
// Save original (pre-compression) captures to device Photos
|
|
52
|
+
if (saveToLibrary) await saveAssetsToLibrary(assets)
|
|
53
|
+
const result = await processAssets(assets, { compress, persistTo })
|
|
54
|
+
return multiple ? result : result[0]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function pickFromLibrary({ multiple = false, maxCount, mediaTypes, persistTo, compress } = {}) {
|
|
58
|
+
const assets = await openLibrary({
|
|
59
|
+
allowsMultipleSelection: multiple,
|
|
60
|
+
selectionLimit: maxCount || 0,
|
|
61
|
+
mediaTypes,
|
|
62
|
+
})
|
|
63
|
+
if (!assets.length) return multiple ? [] : null
|
|
64
|
+
const result = await processAssets(assets, { compress, persistTo })
|
|
65
|
+
return multiple ? result : result[0]
|
|
66
|
+
}
|
package/src/helpers/storage.js
CHANGED
|
@@ -41,6 +41,21 @@ function getAsync(key, defaultValue) {
|
|
|
41
41
|
})
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function notifyAll(value) {
|
|
45
|
+
Object.keys(listeners).forEach((key) => notify(key, value))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function clear() {
|
|
49
|
+
AbsStorage.clear()
|
|
50
|
+
notifyAll(undefined)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clearAsync() {
|
|
54
|
+
return AbsStorage.clearAsync().then(() => {
|
|
55
|
+
notifyAll(undefined)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
function formatStoragedValue(value) {
|
|
45
60
|
try {
|
|
46
61
|
if (!value) return value
|
|
@@ -73,5 +88,7 @@ export const Storage = {
|
|
|
73
88
|
setAsync,
|
|
74
89
|
get,
|
|
75
90
|
getAsync,
|
|
91
|
+
clear,
|
|
92
|
+
clearAsync,
|
|
76
93
|
useState,
|
|
77
94
|
}
|
package/src/i18n/I18n.js
CHANGED
|
@@ -26,7 +26,7 @@ export class I18n {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
t(key, opts = {}) {
|
|
29
|
-
let { ns = 'common', context, ...vars } = opts
|
|
29
|
+
let { ns = 'common', context, default: defaultVal, ...vars } = opts
|
|
30
30
|
const count = vars?.count
|
|
31
31
|
if (key.includes(':')) {
|
|
32
32
|
const splittedKey = key.split(':')
|
|
@@ -39,7 +39,7 @@ export class I18n {
|
|
|
39
39
|
|
|
40
40
|
if (!value) {
|
|
41
41
|
const fallbackData = this.resources[this.fallback]?.[ns]
|
|
42
|
-
value = this._resolveKey(fallbackData, key, count, context)
|
|
42
|
+
value = this._resolveKey(fallbackData, key, count, context) ?? defaultVal ?? key
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
return this._interpolate(value, vars)
|
package/src/index.js
CHANGED
package/src/modifiers/flex.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { clearProps, flattenStyle } from './_helpers'
|
|
2
2
|
|
|
3
3
|
export function useFlexModifier([values, props]) {
|
|
4
|
-
let { flex, ...restProps } = props
|
|
4
|
+
let { flex, alignSelf, selfStretch, selfCenter, selfStart, selfEnd, ...restProps } = props
|
|
5
5
|
|
|
6
6
|
if (flex === true) flex = 1
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
if (selfStretch) alignSelf = 'stretch'
|
|
9
|
+
if (selfCenter) alignSelf = 'center'
|
|
10
|
+
if (selfStart) alignSelf = 'flex-start'
|
|
11
|
+
if (selfEnd) alignSelf = 'flex-end'
|
|
12
|
+
|
|
13
|
+
const style = clearProps({ flex, minWidth: 0, alignSelf })
|
|
9
14
|
|
|
10
15
|
return [
|
|
11
16
|
values,
|
|
@@ -27,6 +27,20 @@ export function useGetResponsiveValue() {
|
|
|
27
27
|
|
|
28
28
|
if (!isObj) return value
|
|
29
29
|
|
|
30
|
+
// Only treat the object as a responsive descriptor when it actually
|
|
31
|
+
// carries breakpoint / platform / df keys. Otherwise it is a plain
|
|
32
|
+
// object prop (e.g. titleProps) and must pass through untouched.
|
|
33
|
+
const bpNames = breakpoints.map((b) => b.name)
|
|
34
|
+
const isResponsiveKey = (k) =>
|
|
35
|
+
k === 'df' ||
|
|
36
|
+
k === 'native' ||
|
|
37
|
+
k === 'web' ||
|
|
38
|
+
k === 'ios' ||
|
|
39
|
+
k === 'android' ||
|
|
40
|
+
bpNames.includes(k) ||
|
|
41
|
+
(/^(\w+)[du]$/.test(k) && bpNames.includes(k.slice(0, -1)))
|
|
42
|
+
if (!Object.keys(value).some(isResponsiveKey)) return value
|
|
43
|
+
|
|
30
44
|
if (value[screen]) return value[screen]
|
|
31
45
|
|
|
32
46
|
const keys = Object.keys(value)
|