@shaxpir/duiduidui-models 1.5.14 → 1.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/models/Condition.d.ts +48 -0
- package/dist/models/Condition.js +102 -1
- package/dist/models/Content.d.ts +2 -2
- package/dist/models/ContentKind.d.ts +1 -1
- package/dist/models/ContentKind.js +1 -1
- package/dist/models/Device.d.ts +34 -9
- package/dist/models/Device.js +101 -29
- package/dist/models/Image.d.ts +31 -0
- package/dist/models/{Media.js → Image.js} +18 -18
- package/dist/models/Manifest.d.ts +1 -1
- package/dist/models/Manifest.js +1 -1
- package/dist/models/Profile.d.ts +7 -7
- package/dist/models/Profile.js +8 -8
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +1 -1
- package/dist/repo/ShareSync.js +2 -2
- package/package.json +1 -1
- package/dist/models/Media.d.ts +0 -31
|
@@ -72,4 +72,52 @@ export declare const Condition: {
|
|
|
72
72
|
intermediate: () => DifficultyCondition;
|
|
73
73
|
advanced: () => DifficultyCondition;
|
|
74
74
|
expert: () => DifficultyCondition;
|
|
75
|
+
/**
|
|
76
|
+
* Check if starred condition is required (in 'all' section)
|
|
77
|
+
*/
|
|
78
|
+
requiresStarred: (filters?: ConditionFilters) => boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Check if starred condition is optional (in 'any' section)
|
|
81
|
+
*/
|
|
82
|
+
allowsStarred: (filters?: ConditionFilters) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Check if starred condition is excluded (in 'none' section)
|
|
85
|
+
*/
|
|
86
|
+
excludesStarred: (filters?: ConditionFilters) => boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Check if starred condition exists anywhere in filters
|
|
89
|
+
*/
|
|
90
|
+
hasStarred: (filters?: ConditionFilters) => boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Check if a specific tag is required (in 'all' section)
|
|
93
|
+
*/
|
|
94
|
+
requiresTag: (filters: ConditionFilters | undefined, tag: string) => boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Check if a specific tag is optional (in 'any' section)
|
|
97
|
+
*/
|
|
98
|
+
allowsTag: (filters: ConditionFilters | undefined, tag: string) => boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Check if a specific tag is excluded (in 'none' section)
|
|
101
|
+
*/
|
|
102
|
+
excludesTag: (filters: ConditionFilters | undefined, tag: string) => boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Check if a specific grade is required (in 'all' section)
|
|
105
|
+
*/
|
|
106
|
+
requiresGrade: (filters: ConditionFilters | undefined, grade: "A" | "B" | "C" | "D" | "F") => boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Check if any condition of a specific type exists in filters
|
|
109
|
+
*/
|
|
110
|
+
hasConditionType: (filters: ConditionFilters | undefined, type: string) => boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Validate filters for logical inconsistencies
|
|
113
|
+
* Returns an array of error messages, empty if valid
|
|
114
|
+
*/
|
|
115
|
+
validate: (filters?: ConditionFilters) => string[];
|
|
116
|
+
hasStarredInAll: (filters?: ConditionFilters) => boolean;
|
|
117
|
+
hasStarredInAny: (filters?: ConditionFilters) => boolean;
|
|
118
|
+
hasStarredInNone: (filters?: ConditionFilters) => boolean;
|
|
119
|
+
hasTagInAll: (filters: ConditionFilters | undefined, tag: string) => boolean;
|
|
120
|
+
hasTagInAny: (filters: ConditionFilters | undefined, tag: string) => boolean;
|
|
121
|
+
hasTagInNone: (filters: ConditionFilters | undefined, tag: string) => boolean;
|
|
122
|
+
hasGradeInAll: (filters: ConditionFilters | undefined, grade: "A" | "B" | "C" | "D" | "F") => boolean;
|
|
75
123
|
};
|
package/dist/models/Condition.js
CHANGED
|
@@ -37,5 +37,106 @@ exports.Condition = {
|
|
|
37
37
|
elementary: () => ({ type: 'difficulty', min: 100, max: 500 }), // Easy
|
|
38
38
|
intermediate: () => ({ type: 'difficulty', min: 500, max: 1500 }), // Medium
|
|
39
39
|
advanced: () => ({ type: 'difficulty', min: 1500, max: 3000 }), // Hard
|
|
40
|
-
expert: () => ({ type: 'difficulty', min: 3000 }) // Very hard
|
|
40
|
+
expert: () => ({ type: 'difficulty', min: 3000 }), // Very hard
|
|
41
|
+
// Helper methods to check if conditions are present in filters
|
|
42
|
+
/**
|
|
43
|
+
* Check if starred condition is required (in 'all' section)
|
|
44
|
+
*/
|
|
45
|
+
requiresStarred: (filters) => {
|
|
46
|
+
return !!(filters?.all?.some(c => c.type === 'starred' && c.value === true));
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* Check if starred condition is optional (in 'any' section)
|
|
50
|
+
*/
|
|
51
|
+
allowsStarred: (filters) => {
|
|
52
|
+
return !!(filters?.any?.some(c => c.type === 'starred' && c.value === true));
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Check if starred condition is excluded (in 'none' section)
|
|
56
|
+
*/
|
|
57
|
+
excludesStarred: (filters) => {
|
|
58
|
+
return !!(filters?.none?.some(c => c.type === 'starred' && c.value === true));
|
|
59
|
+
},
|
|
60
|
+
/**
|
|
61
|
+
* Check if starred condition exists anywhere in filters
|
|
62
|
+
*/
|
|
63
|
+
hasStarred: (filters) => {
|
|
64
|
+
return exports.Condition.requiresStarred(filters) ||
|
|
65
|
+
exports.Condition.allowsStarred(filters) ||
|
|
66
|
+
exports.Condition.excludesStarred(filters);
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* Check if a specific tag is required (in 'all' section)
|
|
70
|
+
*/
|
|
71
|
+
requiresTag: (filters, tag) => {
|
|
72
|
+
return !!(filters?.all?.some(c => c.type === 'tag' && c.tag === tag));
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Check if a specific tag is optional (in 'any' section)
|
|
76
|
+
*/
|
|
77
|
+
allowsTag: (filters, tag) => {
|
|
78
|
+
return !!(filters?.any?.some(c => c.type === 'tag' && c.tag === tag));
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Check if a specific tag is excluded (in 'none' section)
|
|
82
|
+
*/
|
|
83
|
+
excludesTag: (filters, tag) => {
|
|
84
|
+
return !!(filters?.none?.some(c => c.type === 'tag' && c.tag === tag));
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Check if a specific grade is required (in 'all' section)
|
|
88
|
+
*/
|
|
89
|
+
requiresGrade: (filters, grade) => {
|
|
90
|
+
return !!(filters?.all?.some(c => c.type === 'grade' && c.grade === grade));
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Check if any condition of a specific type exists in filters
|
|
94
|
+
*/
|
|
95
|
+
hasConditionType: (filters, type) => {
|
|
96
|
+
return !!(filters?.all?.some(c => c.type === type) ||
|
|
97
|
+
filters?.any?.some(c => c.type === type) ||
|
|
98
|
+
filters?.none?.some(c => c.type === type));
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* Validate filters for logical inconsistencies
|
|
102
|
+
* Returns an array of error messages, empty if valid
|
|
103
|
+
*/
|
|
104
|
+
validate: (filters) => {
|
|
105
|
+
if (!filters)
|
|
106
|
+
return [];
|
|
107
|
+
const errors = [];
|
|
108
|
+
// Check for contradictory starred conditions
|
|
109
|
+
if (exports.Condition.requiresStarred(filters) && exports.Condition.excludesStarred(filters)) {
|
|
110
|
+
errors.push('Cannot require starred items and exclude starred items at the same time');
|
|
111
|
+
}
|
|
112
|
+
// Check for contradictory tags
|
|
113
|
+
const allTags = filters.all?.filter(c => c.type === 'tag').map(c => c.tag) || [];
|
|
114
|
+
const noneTags = filters.none?.filter(c => c.type === 'tag').map(c => c.tag) || [];
|
|
115
|
+
allTags.forEach(tag => {
|
|
116
|
+
if (noneTags.includes(tag)) {
|
|
117
|
+
errors.push(`Tag "${tag}" cannot be both required and excluded`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Check for contradictory grades
|
|
121
|
+
const allGrades = filters.all?.filter(c => c.type === 'grade').map(c => c.grade) || [];
|
|
122
|
+
const noneGrades = filters.none?.filter(c => c.type === 'grade').map(c => c.grade) || [];
|
|
123
|
+
allGrades.forEach(grade => {
|
|
124
|
+
if (noneGrades.includes(grade)) {
|
|
125
|
+
errors.push(`Grade "${grade}" cannot be both required and excluded`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Check for multiple required grades (can only have one grade at a time)
|
|
129
|
+
if (allGrades.length > 1) {
|
|
130
|
+
errors.push(`Cannot require multiple grades: ${allGrades.join(', ')} - an item can only have one grade`);
|
|
131
|
+
}
|
|
132
|
+
return errors;
|
|
133
|
+
},
|
|
134
|
+
// Backward compatibility aliases (deprecated - use requiresStarred/allowsStarred/excludesStarred instead)
|
|
135
|
+
hasStarredInAll: (filters) => exports.Condition.requiresStarred(filters),
|
|
136
|
+
hasStarredInAny: (filters) => exports.Condition.allowsStarred(filters),
|
|
137
|
+
hasStarredInNone: (filters) => exports.Condition.excludesStarred(filters),
|
|
138
|
+
hasTagInAll: (filters, tag) => exports.Condition.requiresTag(filters, tag),
|
|
139
|
+
hasTagInAny: (filters, tag) => exports.Condition.allowsTag(filters, tag),
|
|
140
|
+
hasTagInNone: (filters, tag) => exports.Condition.excludesTag(filters, tag),
|
|
141
|
+
hasGradeInAll: (filters, grade) => exports.Condition.requiresGrade(filters, grade)
|
|
41
142
|
};
|
package/dist/models/Content.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ShareSync } from '../repo';
|
|
|
4
4
|
import { BillingPayload } from './Billing';
|
|
5
5
|
import { ContentKind } from './ContentKind';
|
|
6
6
|
import { DevicePayload } from './Device';
|
|
7
|
-
import {
|
|
7
|
+
import { ImagePayload } from './Image';
|
|
8
8
|
import { MetricPayload } from './Metric';
|
|
9
9
|
import { Model } from './Model';
|
|
10
10
|
import { ProfilePayload } from "./Profile";
|
|
@@ -36,7 +36,7 @@ export interface ContentMeta {
|
|
|
36
36
|
created_at: MultiTime;
|
|
37
37
|
updated_at: MultiTime;
|
|
38
38
|
}
|
|
39
|
-
export type ContentPayload = BillingPayload | DevicePayload |
|
|
39
|
+
export type ContentPayload = BillingPayload | DevicePayload | ImagePayload | MetricPayload | ProfilePayload | ProgressPayload | SessionPayload | TermPayload | UserPayload | WorkspacePayload;
|
|
40
40
|
export declare abstract class Content extends Model {
|
|
41
41
|
static ID_LENGTH: number;
|
|
42
42
|
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
@@ -13,7 +13,7 @@ var ContentKind;
|
|
|
13
13
|
ContentKind["SESSION"] = "session";
|
|
14
14
|
ContentKind["TERM"] = "term";
|
|
15
15
|
ContentKind["USER"] = "user";
|
|
16
|
-
ContentKind["
|
|
16
|
+
ContentKind["IMAGE"] = "image";
|
|
17
17
|
ContentKind["WORKSPACE"] = "workspace";
|
|
18
18
|
// These are used in the ShareDB system, but for internal bookkeeping, not for Content subclasses.
|
|
19
19
|
ContentKind["MANIFEST"] = "manifest";
|
package/dist/models/Device.d.ts
CHANGED
|
@@ -6,27 +6,42 @@ import { ConditionFilters } from './Condition';
|
|
|
6
6
|
export interface LastSync {
|
|
7
7
|
at_utc_time: CompactDateTime | null;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
text: string;
|
|
9
|
+
interface BaseQueueItem {
|
|
11
10
|
added_at: CompactDateTime;
|
|
12
11
|
retry_count: number;
|
|
13
12
|
next_retry_at?: CompactDateTime;
|
|
14
13
|
last_error?: string;
|
|
15
14
|
}
|
|
15
|
+
export interface AudioDownloadQueueItem extends BaseQueueItem {
|
|
16
|
+
type: 'audio';
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ImageDownloadQueueItem extends BaseQueueItem {
|
|
20
|
+
type: 'image';
|
|
21
|
+
image_id: ContentId;
|
|
22
|
+
}
|
|
23
|
+
export type DownloadQueueItem = AudioDownloadQueueItem | ImageDownloadQueueItem;
|
|
24
|
+
export interface ImageUploadQueueItem extends BaseQueueItem {
|
|
25
|
+
type: 'image';
|
|
26
|
+
image_id: ContentId;
|
|
27
|
+
}
|
|
28
|
+
export type UploadQueueItem = ImageUploadQueueItem;
|
|
16
29
|
export interface DevicePayload {
|
|
17
30
|
last_sync?: LastSync;
|
|
18
31
|
chinese_font?: string;
|
|
19
32
|
raw_search_text?: string;
|
|
20
33
|
star_filter?: boolean;
|
|
21
34
|
conditions?: ConditionFilters;
|
|
22
|
-
|
|
35
|
+
download_queue?: DownloadQueueItem[];
|
|
36
|
+
upload_queue?: UploadQueueItem[];
|
|
23
37
|
}
|
|
24
38
|
export interface DeviceBody extends ContentBody {
|
|
25
39
|
meta: ContentMeta;
|
|
26
40
|
payload: DevicePayload;
|
|
27
41
|
}
|
|
28
42
|
export declare class Device extends Content {
|
|
29
|
-
private
|
|
43
|
+
private _downloadQueueView;
|
|
44
|
+
private _uploadQueueView;
|
|
30
45
|
static create(userId: ContentId, deviceId: ContentId): Device;
|
|
31
46
|
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
32
47
|
get payload(): DevicePayload;
|
|
@@ -34,9 +49,19 @@ export declare class Device extends Content {
|
|
|
34
49
|
setLastSyncAtUtcTime(value: CompactDateTime): void;
|
|
35
50
|
get chineseFont(): string;
|
|
36
51
|
setChineseFont(value: string): void;
|
|
37
|
-
get
|
|
38
|
-
get
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
get downloadQueue(): DownloadQueueItem[];
|
|
53
|
+
get downloadQueueLength(): number;
|
|
54
|
+
addToDownloadQueue(item: AudioDownloadQueueItem | ImageDownloadQueueItem): void;
|
|
55
|
+
removeFromDownloadQueue(predicate: (item: DownloadQueueItem) => boolean): void;
|
|
56
|
+
updateDownloadQueueItem(predicate: (item: DownloadQueueItem) => boolean, updates: Partial<BaseQueueItem>): void;
|
|
57
|
+
addAudioToDownloadQueue(text: string): void;
|
|
58
|
+
removeAudioFromDownloadQueue(text: string): void;
|
|
59
|
+
get uploadQueue(): UploadQueueItem[];
|
|
60
|
+
get uploadQueueLength(): number;
|
|
61
|
+
addToUploadQueue(item: UploadQueueItem): void;
|
|
62
|
+
removeFromUploadQueue(predicate: (item: UploadQueueItem) => boolean): void;
|
|
63
|
+
updateUploadQueueItem(predicate: (item: UploadQueueItem) => boolean, updates: Partial<BaseQueueItem>): void;
|
|
64
|
+
addImageToUploadQueue(imageId: ContentId): void;
|
|
65
|
+
removeImageFromUploadQueue(imageId: ContentId): void;
|
|
42
66
|
}
|
|
67
|
+
export {};
|
package/dist/models/Device.js
CHANGED
|
@@ -21,13 +21,15 @@ class Device extends Content_1.Content {
|
|
|
21
21
|
},
|
|
22
22
|
payload: {
|
|
23
23
|
last_sync: { at_utc_time: null },
|
|
24
|
-
|
|
24
|
+
download_queue: [],
|
|
25
|
+
upload_queue: []
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
constructor(doc, shouldAcquire, shareSync) {
|
|
29
30
|
super(doc, shouldAcquire, shareSync);
|
|
30
|
-
this.
|
|
31
|
+
this._downloadQueueView = new ArrayView_1.ArrayView(this, ['payload', 'download_queue']);
|
|
32
|
+
this._uploadQueueView = new ArrayView_1.ArrayView(this, ['payload', 'upload_queue']);
|
|
31
33
|
}
|
|
32
34
|
get payload() {
|
|
33
35
|
this.checkDisposed("Device.payload");
|
|
@@ -57,50 +59,120 @@ class Device extends Content_1.Content {
|
|
|
57
59
|
batch.commit();
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
// Download Queue Methods
|
|
63
|
+
get downloadQueue() {
|
|
64
|
+
this.checkDisposed("Device.downloadQueue");
|
|
65
|
+
return this._downloadQueueView.values;
|
|
63
66
|
}
|
|
64
|
-
get
|
|
65
|
-
this.checkDisposed("Device.
|
|
66
|
-
return this.
|
|
67
|
+
get downloadQueueLength() {
|
|
68
|
+
this.checkDisposed("Device.downloadQueueLength");
|
|
69
|
+
return this._downloadQueueView.length;
|
|
67
70
|
}
|
|
68
|
-
|
|
69
|
-
this.checkDisposed("Device.
|
|
71
|
+
addToDownloadQueue(item) {
|
|
72
|
+
this.checkDisposed("Device.addToDownloadQueue");
|
|
70
73
|
// Check if item already exists in queue
|
|
71
|
-
const existingIndex = this.
|
|
74
|
+
const existingIndex = this._downloadQueueView.values.findIndex(queueItem => {
|
|
75
|
+
if (queueItem.type === 'audio' && item.type === 'audio') {
|
|
76
|
+
return queueItem.text === item.text;
|
|
77
|
+
}
|
|
78
|
+
else if (queueItem.type === 'image' && item.type === 'image') {
|
|
79
|
+
return queueItem.image_id === item.image_id;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
});
|
|
72
83
|
if (existingIndex !== -1) {
|
|
73
|
-
console.log(`
|
|
84
|
+
console.log(`Download already queued for: ${item.type}`, item);
|
|
74
85
|
return;
|
|
75
86
|
}
|
|
87
|
+
this._downloadQueueView.push(item);
|
|
88
|
+
console.log(`Added to download queue: ${item.type}`, item);
|
|
89
|
+
}
|
|
90
|
+
removeFromDownloadQueue(predicate) {
|
|
91
|
+
this.checkDisposed("Device.removeFromDownloadQueue");
|
|
92
|
+
const itemIndex = this._downloadQueueView.values.findIndex(predicate);
|
|
93
|
+
if (itemIndex !== -1) {
|
|
94
|
+
this._downloadQueueView.removeAt(itemIndex);
|
|
95
|
+
console.log(`Removed from download queue at index ${itemIndex}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
updateDownloadQueueItem(predicate, updates) {
|
|
99
|
+
this.checkDisposed("Device.updateDownloadQueueItem");
|
|
100
|
+
const itemIndex = this._downloadQueueView.values.findIndex(predicate);
|
|
101
|
+
if (itemIndex !== -1) {
|
|
102
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
103
|
+
this._downloadQueueView.setObjectValueAtIndex(itemIndex, key, value);
|
|
104
|
+
}
|
|
105
|
+
console.log(`Updated download queue item at index ${itemIndex}`, updates);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Convenience methods for audio downloads (backward compatibility)
|
|
109
|
+
addAudioToDownloadQueue(text) {
|
|
76
110
|
const now = shaxpir_common_1.ClockService.getClock().utc();
|
|
77
|
-
|
|
111
|
+
this.addToDownloadQueue({
|
|
112
|
+
type: 'audio',
|
|
78
113
|
text,
|
|
79
114
|
added_at: now,
|
|
80
115
|
retry_count: 0
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
removeAudioFromDownloadQueue(text) {
|
|
119
|
+
this.removeFromDownloadQueue(item => item.type === 'audio' && item.text === text);
|
|
120
|
+
}
|
|
121
|
+
// Upload Queue Methods
|
|
122
|
+
get uploadQueue() {
|
|
123
|
+
this.checkDisposed("Device.uploadQueue");
|
|
124
|
+
return this._uploadQueueView.values;
|
|
125
|
+
}
|
|
126
|
+
get uploadQueueLength() {
|
|
127
|
+
this.checkDisposed("Device.uploadQueueLength");
|
|
128
|
+
return this._uploadQueueView.length;
|
|
129
|
+
}
|
|
130
|
+
addToUploadQueue(item) {
|
|
131
|
+
this.checkDisposed("Device.addToUploadQueue");
|
|
132
|
+
// Check if item already exists in queue
|
|
133
|
+
const existingIndex = this._uploadQueueView.values.findIndex(queueItem => {
|
|
134
|
+
if (queueItem.type === 'image' && item.type === 'image') {
|
|
135
|
+
return queueItem.image_id === item.image_id;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
if (existingIndex !== -1) {
|
|
140
|
+
console.log(`Upload already queued for: ${item.type}`, item);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this._uploadQueueView.push(item);
|
|
144
|
+
console.log(`Added to upload queue: ${item.type}`, item);
|
|
145
|
+
}
|
|
146
|
+
removeFromUploadQueue(predicate) {
|
|
147
|
+
this.checkDisposed("Device.removeFromUploadQueue");
|
|
148
|
+
const itemIndex = this._uploadQueueView.values.findIndex(predicate);
|
|
89
149
|
if (itemIndex !== -1) {
|
|
90
|
-
this.
|
|
91
|
-
console.log(`Removed from
|
|
150
|
+
this._uploadQueueView.removeAt(itemIndex);
|
|
151
|
+
console.log(`Removed from upload queue at index ${itemIndex}`);
|
|
92
152
|
}
|
|
93
153
|
}
|
|
94
|
-
|
|
95
|
-
this.checkDisposed("Device.
|
|
96
|
-
const itemIndex = this.
|
|
154
|
+
updateUploadQueueItem(predicate, updates) {
|
|
155
|
+
this.checkDisposed("Device.updateUploadQueueItem");
|
|
156
|
+
const itemIndex = this._uploadQueueView.values.findIndex(predicate);
|
|
97
157
|
if (itemIndex !== -1) {
|
|
98
|
-
// Update each field in the updates object
|
|
99
158
|
for (const [key, value] of Object.entries(updates)) {
|
|
100
|
-
this.
|
|
159
|
+
this._uploadQueueView.setObjectValueAtIndex(itemIndex, key, value);
|
|
101
160
|
}
|
|
102
|
-
console.log(`Updated
|
|
161
|
+
console.log(`Updated upload queue item at index ${itemIndex}`, updates);
|
|
103
162
|
}
|
|
104
163
|
}
|
|
164
|
+
// Convenience methods for image uploads
|
|
165
|
+
addImageToUploadQueue(imageId) {
|
|
166
|
+
const now = shaxpir_common_1.ClockService.getClock().utc();
|
|
167
|
+
this.addToUploadQueue({
|
|
168
|
+
type: 'image',
|
|
169
|
+
image_id: imageId,
|
|
170
|
+
added_at: now,
|
|
171
|
+
retry_count: 0
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
removeImageFromUploadQueue(imageId) {
|
|
175
|
+
this.removeFromUploadQueue(item => item.type === 'image' && item.image_id === imageId);
|
|
176
|
+
}
|
|
105
177
|
}
|
|
106
178
|
exports.Device = Device;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
+
import { ShareSync } from '../repo';
|
|
3
|
+
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
4
|
+
export interface ImageDimensions {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ImageCropping extends ImageDimensions {
|
|
9
|
+
offset_top: number;
|
|
10
|
+
offset_left: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ImagePayload extends ImageDimensions {
|
|
13
|
+
extension: string;
|
|
14
|
+
bytes: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ImageBody extends ContentBody {
|
|
17
|
+
meta: ContentMeta;
|
|
18
|
+
payload: ImagePayload;
|
|
19
|
+
}
|
|
20
|
+
export declare class Image extends Content {
|
|
21
|
+
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
22
|
+
get payload(): ImagePayload;
|
|
23
|
+
static create(userId: ContentId, imageId: ContentId, payload: ImagePayload): Image;
|
|
24
|
+
get bytes(): number;
|
|
25
|
+
get width(): number;
|
|
26
|
+
get height(): number;
|
|
27
|
+
get extension(): string;
|
|
28
|
+
get mimeType(): string;
|
|
29
|
+
scaleToFit(boxSize: number, cropping: ImageCropping): ImageCropping;
|
|
30
|
+
static makeDefaultCropping(image: Image): ImageCropping;
|
|
31
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Image = void 0;
|
|
4
4
|
const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
|
|
5
5
|
const repo_1 = require("../repo");
|
|
6
6
|
const Content_1 = require("./Content");
|
|
7
7
|
const ContentKind_1 = require("./ContentKind");
|
|
8
|
-
class
|
|
8
|
+
class Image extends Content_1.Content {
|
|
9
9
|
constructor(doc, shouldAcquire, shareSync) {
|
|
10
10
|
super(doc, shouldAcquire, shareSync);
|
|
11
11
|
}
|
|
12
12
|
get payload() {
|
|
13
|
-
this.checkDisposed("
|
|
13
|
+
this.checkDisposed("Image.payload");
|
|
14
14
|
return this.doc.data.payload;
|
|
15
15
|
}
|
|
16
|
-
static create(userId,
|
|
16
|
+
static create(userId, imageId, payload) {
|
|
17
17
|
const now = shaxpir_common_1.ClockService.getClock().now();
|
|
18
18
|
return repo_1.ShareSyncFactory.get().createContent({
|
|
19
19
|
meta: {
|
|
20
|
-
ref:
|
|
21
|
-
kind: ContentKind_1.ContentKind.
|
|
22
|
-
id:
|
|
20
|
+
ref: imageId,
|
|
21
|
+
kind: ContentKind_1.ContentKind.IMAGE,
|
|
22
|
+
id: imageId,
|
|
23
23
|
owner: userId,
|
|
24
24
|
created_at: now,
|
|
25
25
|
updated_at: now
|
|
@@ -28,23 +28,23 @@ class Media extends Content_1.Content {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
get bytes() {
|
|
31
|
-
this.checkDisposed("
|
|
31
|
+
this.checkDisposed("Image.bytes");
|
|
32
32
|
return this.payload.bytes;
|
|
33
33
|
}
|
|
34
34
|
get width() {
|
|
35
|
-
this.checkDisposed("
|
|
35
|
+
this.checkDisposed("Image.width");
|
|
36
36
|
return this.payload.width;
|
|
37
37
|
}
|
|
38
38
|
get height() {
|
|
39
|
-
this.checkDisposed("
|
|
39
|
+
this.checkDisposed("Image.height");
|
|
40
40
|
return this.payload.height;
|
|
41
41
|
}
|
|
42
42
|
get extension() {
|
|
43
|
-
this.checkDisposed("
|
|
43
|
+
this.checkDisposed("Image.extension");
|
|
44
44
|
return this.payload.extension;
|
|
45
45
|
}
|
|
46
|
-
get
|
|
47
|
-
this.checkDisposed("
|
|
46
|
+
get mimeType() {
|
|
47
|
+
this.checkDisposed("Image.mimeType");
|
|
48
48
|
const extension = this.extension;
|
|
49
49
|
if (extension === "jpg" || extension === "jpeg") {
|
|
50
50
|
return "image/jpeg";
|
|
@@ -64,7 +64,7 @@ class Media extends Content_1.Content {
|
|
|
64
64
|
return "image/" + extension;
|
|
65
65
|
}
|
|
66
66
|
scaleToFit(boxSize, cropping) {
|
|
67
|
-
this.checkDisposed("
|
|
67
|
+
this.checkDisposed("Image.scaleToFit");
|
|
68
68
|
const zoom = boxSize / cropping.width;
|
|
69
69
|
const scaledWidth = Math.floor(this.width * zoom);
|
|
70
70
|
const scaledHeight = Math.floor(this.height * zoom);
|
|
@@ -77,9 +77,9 @@ class Media extends Content_1.Content {
|
|
|
77
77
|
"height": scaledHeight
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
-
static makeDefaultCropping(
|
|
81
|
-
const width =
|
|
82
|
-
const height =
|
|
80
|
+
static makeDefaultCropping(image) {
|
|
81
|
+
const width = image.width;
|
|
82
|
+
const height = image.height;
|
|
83
83
|
const isLandscapeOrSquare = width >= height;
|
|
84
84
|
const isPortraitOrSquare = height >= width;
|
|
85
85
|
const croppingOffsetTop = isLandscapeOrSquare ? 0 : Math.floor((height - width) / 2);
|
|
@@ -94,4 +94,4 @@ class Media extends Content_1.Content {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
exports.
|
|
97
|
+
exports.Image = Image;
|
package/dist/models/Manifest.js
CHANGED
package/dist/models/Profile.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
2
|
import { ShareSync } from '../repo';
|
|
3
3
|
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
4
|
-
import {
|
|
4
|
+
import { ImageCropping } from './Image';
|
|
5
5
|
export interface ProfilePayload {
|
|
6
6
|
username: string;
|
|
7
7
|
full_name: string;
|
|
8
|
-
|
|
9
|
-
avatar_cropping:
|
|
8
|
+
avatar_image_ref: ContentId;
|
|
9
|
+
avatar_cropping: ImageCropping;
|
|
10
10
|
}
|
|
11
11
|
export interface ProfileBody extends ContentBody {
|
|
12
12
|
meta: ContentMeta;
|
|
@@ -21,9 +21,9 @@ export declare class Profile extends Content {
|
|
|
21
21
|
setUsername(value: string): void;
|
|
22
22
|
get fullName(): string;
|
|
23
23
|
setFullName(value: string): void;
|
|
24
|
-
get
|
|
25
|
-
|
|
26
|
-
get avatarCropping():
|
|
27
|
-
setAvatarCropping(value:
|
|
24
|
+
get avatarImageRef(): ContentId;
|
|
25
|
+
setAvatarImageRef(value: ContentId): void;
|
|
26
|
+
get avatarCropping(): ImageCropping;
|
|
27
|
+
setAvatarCropping(value: ImageCropping): void;
|
|
28
28
|
static findByUsername(username: string): Promise<Profile[]>;
|
|
29
29
|
}
|
package/dist/models/Profile.js
CHANGED
|
@@ -25,7 +25,7 @@ class Profile extends Content_1.Content {
|
|
|
25
25
|
payload: {
|
|
26
26
|
username: null,
|
|
27
27
|
full_name: '',
|
|
28
|
-
|
|
28
|
+
avatar_image_ref: null,
|
|
29
29
|
avatar_cropping: null
|
|
30
30
|
}
|
|
31
31
|
});
|
|
@@ -61,15 +61,15 @@ class Profile extends Content_1.Content {
|
|
|
61
61
|
batch.commit();
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
get
|
|
65
|
-
this.checkDisposed("Profile.
|
|
66
|
-
return this.payload.
|
|
64
|
+
get avatarImageRef() {
|
|
65
|
+
this.checkDisposed("Profile.avatarImageRef");
|
|
66
|
+
return this.payload.avatar_image_ref;
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
this.checkDisposed("Profile.
|
|
70
|
-
if (this.
|
|
68
|
+
setAvatarImageRef(value) {
|
|
69
|
+
this.checkDisposed("Profile.setAvatarImageRef");
|
|
70
|
+
if (this.avatarImageRef !== value) {
|
|
71
71
|
const batch = new Operation_1.BatchOperation(this);
|
|
72
|
-
batch.setPathValue(['payload', '
|
|
72
|
+
batch.setPathValue(['payload', 'avatar_image_ref'], value);
|
|
73
73
|
batch.commit();
|
|
74
74
|
}
|
|
75
75
|
}
|
package/dist/models/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './Flag';
|
|
|
10
10
|
export * from './GeoLocation';
|
|
11
11
|
export * from './Hanzi';
|
|
12
12
|
export * from './Manifest';
|
|
13
|
-
export * from './
|
|
13
|
+
export * from './Image';
|
|
14
14
|
export * from './Metric';
|
|
15
15
|
export * from './Model';
|
|
16
16
|
export * from './Operation';
|
package/dist/models/index.js
CHANGED
|
@@ -27,7 +27,7 @@ __exportStar(require("./Flag"), exports);
|
|
|
27
27
|
__exportStar(require("./GeoLocation"), exports);
|
|
28
28
|
__exportStar(require("./Hanzi"), exports);
|
|
29
29
|
__exportStar(require("./Manifest"), exports);
|
|
30
|
-
__exportStar(require("./
|
|
30
|
+
__exportStar(require("./Image"), exports);
|
|
31
31
|
__exportStar(require("./Metric"), exports);
|
|
32
32
|
__exportStar(require("./Model"), exports);
|
|
33
33
|
__exportStar(require("./Operation"), exports);
|
package/dist/repo/ShareSync.js
CHANGED
|
@@ -419,8 +419,8 @@ class ShareSync {
|
|
|
419
419
|
else if (kind === ContentKind_1.ContentKind.METRIC) {
|
|
420
420
|
return new models_1.Metric(doc, shouldAcquire, shareSync);
|
|
421
421
|
}
|
|
422
|
-
else if (kind === ContentKind_1.ContentKind.
|
|
423
|
-
return new models_1.
|
|
422
|
+
else if (kind === ContentKind_1.ContentKind.IMAGE) {
|
|
423
|
+
return new models_1.Image(doc, shouldAcquire, shareSync);
|
|
424
424
|
}
|
|
425
425
|
else if (kind === ContentKind_1.ContentKind.PROFILE) {
|
|
426
426
|
return new models_1.Profile(doc, shouldAcquire, shareSync);
|
package/package.json
CHANGED
package/dist/models/Media.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
-
import { ShareSync } from '../repo';
|
|
3
|
-
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
4
|
-
export interface MediaDimensions {
|
|
5
|
-
width: number;
|
|
6
|
-
height: number;
|
|
7
|
-
}
|
|
8
|
-
export interface MediaCropping extends MediaDimensions {
|
|
9
|
-
offset_top: number;
|
|
10
|
-
offset_left: number;
|
|
11
|
-
}
|
|
12
|
-
export interface MediaPayload extends MediaDimensions {
|
|
13
|
-
extension: string;
|
|
14
|
-
bytes: number;
|
|
15
|
-
}
|
|
16
|
-
export interface MediaBody extends ContentBody {
|
|
17
|
-
meta: ContentMeta;
|
|
18
|
-
payload: MediaPayload;
|
|
19
|
-
}
|
|
20
|
-
export declare class Media extends Content {
|
|
21
|
-
constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
|
|
22
|
-
get payload(): MediaPayload;
|
|
23
|
-
static create(userId: ContentId, mediaId: ContentId, payload: MediaPayload): Media;
|
|
24
|
-
get bytes(): number;
|
|
25
|
-
get width(): number;
|
|
26
|
-
get height(): number;
|
|
27
|
-
get extension(): string;
|
|
28
|
-
get mediaType(): string;
|
|
29
|
-
scaleToFit(boxSize: number, cropping: MediaCropping): MediaCropping;
|
|
30
|
-
static makeDefaultCropping(media: Media): MediaCropping;
|
|
31
|
-
}
|