feat: tags (#11980)

* feat: tags

* fix: folder tree icons

* navigate to tag from detail panel

* delete tag

* Tag position and add tag button

* Tag asset in detail panel

* refactor form

* feat: navigate to tag page from clicking on a tag

* feat: delete tags from the tag page

* refactor: moving tag section in detail panel and add + tag button

* feat: tag asset action in detail panel

* refactor add tag form

* fdisable add tag button when there is no selection

* feat: tag bulk endpoint

* feat: tag colors

* chore: clean up

* chore: unit tests

* feat: write tags to sidecar

* Remove tag and auto focus on tag creation form opened

* chore: regenerate migration

* chore: linting

* add color picker to tag edit form

* fix: force render tags timeline on navigating back from asset viewer

* feat: read tags from keywords

* chore: clean up

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2024-08-29 12:14:03 -04:00
committed by GitHub
parent 682adaa334
commit d08a20bd57
68 changed files with 3032 additions and 814 deletions

View File

@@ -6169,7 +6169,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateTagDto"
"$ref": "#/components/schemas/TagCreateDto"
}
}
},
@@ -6201,6 +6201,91 @@
"tags": [
"Tags"
]
},
"put": {
"operationId": "upsertTags",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TagUpsertDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/TagResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Tags"
]
}
},
"/tags/assets": {
"put": {
"operationId": "bulkTagAssets",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TagBulkAssetsDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TagBulkAssetsResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Tags"
]
}
},
"/tags/{id}": {
@@ -6218,7 +6303,7 @@
}
],
"responses": {
"200": {
"204": {
"description": ""
}
},
@@ -6277,7 +6362,7 @@
"Tags"
]
},
"patch": {
"put": {
"operationId": "updateTag",
"parameters": [
{
@@ -6294,7 +6379,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateTagDto"
"$ref": "#/components/schemas/TagUpdateDto"
}
}
},
@@ -6346,7 +6431,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetIdsDto"
"$ref": "#/components/schemas/BulkIdsDto"
}
}
},
@@ -6358,50 +6443,7 @@
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AssetIdsResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Tags"
]
},
"get": {
"operationId": "getTagAssets",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AssetResponseDto"
"$ref": "#/components/schemas/BulkIdResponseDto"
},
"type": "array"
}
@@ -6442,7 +6484,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetIdsDto"
"$ref": "#/components/schemas/BulkIdsDto"
}
}
},
@@ -6454,7 +6496,7 @@
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AssetIdsResponseDto"
"$ref": "#/components/schemas/BulkIdResponseDto"
},
"type": "array"
}
@@ -6549,6 +6591,15 @@
"$ref": "#/components/schemas/TimeBucketSize"
}
},
{
"name": "tagId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "timeBucket",
"required": true,
@@ -6684,6 +6735,15 @@
"$ref": "#/components/schemas/TimeBucketSize"
}
},
{
"name": "tagId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "userId",
"required": false,
@@ -8685,21 +8745,6 @@
],
"type": "object"
},
"CreateTagDto": {
"properties": {
"name": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/TagTypeEnum"
}
},
"required": [
"name",
"type"
],
"type": "object"
},
"DownloadArchiveInfo": {
"properties": {
"assetIds": {
@@ -10053,6 +10098,7 @@
"tag.read",
"tag.update",
"tag.delete",
"tag.asset",
"admin.user.create",
"admin.user.read",
"admin.user.update",
@@ -11848,36 +11894,113 @@
],
"type": "object"
},
"TagBulkAssetsDto": {
"properties": {
"assetIds": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"tagIds": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"required": [
"assetIds",
"tagIds"
],
"type": "object"
},
"TagBulkAssetsResponseDto": {
"properties": {
"count": {
"type": "integer"
}
},
"required": [
"count"
],
"type": "object"
},
"TagCreateDto": {
"properties": {
"color": {
"type": "string"
},
"name": {
"type": "string"
},
"parentId": {
"format": "uuid",
"nullable": true,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"TagResponseDto": {
"properties": {
"color": {
"type": "string"
},
"createdAt": {
"format": "date-time",
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/TagTypeEnum"
"updatedAt": {
"format": "date-time",
"type": "string"
},
"userId": {
"value": {
"type": "string"
}
},
"required": [
"createdAt",
"id",
"name",
"type",
"userId"
"updatedAt",
"value"
],
"type": "object"
},
"TagTypeEnum": {
"enum": [
"OBJECT",
"FACE",
"CUSTOM"
"TagUpdateDto": {
"properties": {
"color": {
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"TagUpsertDto": {
"properties": {
"tags": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"tags"
],
"type": "string"
"type": "object"
},
"TimeBucketResponseDto": {
"properties": {
@@ -12021,14 +12144,6 @@
],
"type": "object"
},
"UpdateTagDto": {
"properties": {
"name": {
"type": "string"
}
},
"type": "object"
},
"UsageByUserDto": {
"properties": {
"photos": {

View File

@@ -198,10 +198,12 @@ export type AssetStackResponseDto = {
primaryAssetId: string;
};
export type TagResponseDto = {
color?: string;
createdAt: string;
id: string;
name: string;
"type": TagTypeEnum;
userId: string;
updatedAt: string;
value: string;
};
export type AssetResponseDto = {
/** base64 encoded sha1 hash */
@@ -1171,12 +1173,23 @@ export type ReverseGeocodingStateResponseDto = {
lastImportFileName: string | null;
lastUpdate: string | null;
};
export type CreateTagDto = {
export type TagCreateDto = {
color?: string;
name: string;
"type": TagTypeEnum;
parentId?: string | null;
};
export type UpdateTagDto = {
name?: string;
export type TagUpsertDto = {
tags: string[];
};
export type TagBulkAssetsDto = {
assetIds: string[];
tagIds: string[];
};
export type TagBulkAssetsResponseDto = {
count: number;
};
export type TagUpdateDto = {
color?: string | null;
};
export type TimeBucketResponseDto = {
count: number;
@@ -2835,8 +2848,8 @@ export function getAllTags(opts?: Oazapfts.RequestOpts) {
...opts
}));
}
export function createTag({ createTagDto }: {
createTagDto: CreateTagDto;
export function createTag({ tagCreateDto }: {
tagCreateDto: TagCreateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
@@ -2844,7 +2857,31 @@ export function createTag({ createTagDto }: {
}>("/tags", oazapfts.json({
...opts,
method: "POST",
body: createTagDto
body: tagCreateDto
})));
}
export function upsertTags({ tagUpsertDto }: {
tagUpsertDto: TagUpsertDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TagResponseDto[];
}>("/tags", oazapfts.json({
...opts,
method: "PUT",
body: tagUpsertDto
})));
}
export function bulkTagAssets({ tagBulkAssetsDto }: {
tagBulkAssetsDto: TagBulkAssetsDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TagBulkAssetsResponseDto;
}>("/tags/assets", oazapfts.json({
...opts,
method: "PUT",
body: tagBulkAssetsDto
})));
}
export function deleteTag({ id }: {
@@ -2865,56 +2902,46 @@ export function getTagById({ id }: {
...opts
}));
}
export function updateTag({ id, updateTagDto }: {
export function updateTag({ id, tagUpdateDto }: {
id: string;
updateTagDto: UpdateTagDto;
tagUpdateDto: TagUpdateDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: TagResponseDto;
}>(`/tags/${encodeURIComponent(id)}`, oazapfts.json({
...opts,
method: "PATCH",
body: updateTagDto
method: "PUT",
body: tagUpdateDto
})));
}
export function untagAssets({ id, assetIdsDto }: {
export function untagAssets({ id, bulkIdsDto }: {
id: string;
assetIdsDto: AssetIdsDto;
bulkIdsDto: BulkIdsDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetIdsResponseDto[];
data: BulkIdResponseDto[];
}>(`/tags/${encodeURIComponent(id)}/assets`, oazapfts.json({
...opts,
method: "DELETE",
body: assetIdsDto
body: bulkIdsDto
})));
}
export function getTagAssets({ id }: {
export function tagAssets({ id, bulkIdsDto }: {
id: string;
bulkIdsDto: BulkIdsDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetResponseDto[];
}>(`/tags/${encodeURIComponent(id)}/assets`, {
...opts
}));
}
export function tagAssets({ id, assetIdsDto }: {
id: string;
assetIdsDto: AssetIdsDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: AssetIdsResponseDto[];
data: BulkIdResponseDto[];
}>(`/tags/${encodeURIComponent(id)}/assets`, oazapfts.json({
...opts,
method: "PUT",
body: assetIdsDto
body: bulkIdsDto
})));
}
export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, timeBucket, userId, withPartners, withStacked }: {
export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, withPartners, withStacked }: {
albumId?: string;
isArchived?: boolean;
isFavorite?: boolean;
@@ -2923,6 +2950,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
order?: AssetOrder;
personId?: string;
size: TimeBucketSize;
tagId?: string;
timeBucket: string;
userId?: string;
withPartners?: boolean;
@@ -2940,6 +2968,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
order,
personId,
size,
tagId,
timeBucket,
userId,
withPartners,
@@ -2948,7 +2977,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
...opts
}));
}
export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, userId, withPartners, withStacked }: {
export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, userId, withPartners, withStacked }: {
albumId?: string;
isArchived?: boolean;
isFavorite?: boolean;
@@ -2957,6 +2986,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
order?: AssetOrder;
personId?: string;
size: TimeBucketSize;
tagId?: string;
userId?: string;
withPartners?: boolean;
withStacked?: boolean;
@@ -2973,6 +3003,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
order,
personId,
size,
tagId,
userId,
withPartners,
withStacked
@@ -3162,11 +3193,6 @@ export enum AlbumUserRole {
Editor = "editor",
Viewer = "viewer"
}
export enum TagTypeEnum {
Object = "OBJECT",
Face = "FACE",
Custom = "CUSTOM"
}
export enum AssetTypeEnum {
Image = "IMAGE",
Video = "VIDEO",
@@ -3257,6 +3283,7 @@ export enum Permission {
TagRead = "tag.read",
TagUpdate = "tag.update",
TagDelete = "tag.delete",
TagAsset = "tag.asset",
AdminUserCreate = "admin.user.create",
AdminUserRead = "admin.user.read",
AdminUserUpdate = "admin.user.update",