mirror of
https://github.com/modrinth/code.git
synced 2026-08-02 14:15:53 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9044e5794 | ||
|
|
de0f596ed5 |
@@ -235,10 +235,7 @@ async function clearAllGroups() {
|
||||
const groups = await client.labrinth.attribution_internal.listProjectAttribution(
|
||||
props.project_id,
|
||||
)
|
||||
|
||||
for (const group of groups) {
|
||||
await client.labrinth.attribution_internal.deleteGroup(group.id)
|
||||
}
|
||||
await client.labrinth.attribution_internal.deleteGroups(groups.map((group) => group.id))
|
||||
|
||||
await queryClient.invalidateQueries({ queryKey: ['project-attribution', props.project_id] })
|
||||
} catch (error) {
|
||||
|
||||
@@ -3905,6 +3905,21 @@
|
||||
"project.settings.permissions.completed.title": {
|
||||
"message": "Permissions completed!"
|
||||
},
|
||||
"project.settings.permissions.delete-all-groups": {
|
||||
"message": "Delete all groups"
|
||||
},
|
||||
"project.settings.permissions.delete-all-groups-confirmation.description": {
|
||||
"message": "This will permanently delete all {count, plural, one {# attribution group} other {# attribution groups}} for this project and all files inside them. This action cannot be undone."
|
||||
},
|
||||
"project.settings.permissions.delete-all-groups-confirmation.title": {
|
||||
"message": "Delete all attribution groups?"
|
||||
},
|
||||
"project.settings.permissions.delete-all-groups-error": {
|
||||
"message": "Could not delete all attribution groups"
|
||||
},
|
||||
"project.settings.permissions.delete-all-groups-success": {
|
||||
"message": "All attribution groups were deleted."
|
||||
},
|
||||
"project.settings.permissions.empty-state.description": {
|
||||
"message": "None of your project's versions contain external content, so you don't need to worry about obtaining permissions."
|
||||
},
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
RightArrowIcon,
|
||||
ScaleIcon,
|
||||
SearchIcon,
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
UnfoldVerticalIcon,
|
||||
XCircleIcon,
|
||||
} from '@modrinth/assets'
|
||||
@@ -18,20 +20,22 @@ import {
|
||||
Combobox,
|
||||
type ComboboxOption,
|
||||
commonMessages,
|
||||
ConfirmModal,
|
||||
createAttributionGroupTitle,
|
||||
defineMessage,
|
||||
defineMessages,
|
||||
EmptyState,
|
||||
ExternalProjectPermissionsCard,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
injectProjectPageContext,
|
||||
IntlFormatted,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { isStaff } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import { setupAttributionModerationProvider } from '~/providers/setup/attribution-moderation'
|
||||
|
||||
@@ -47,6 +51,10 @@ const isModerator = computed(() => {
|
||||
const { formatMessage } = useVIntl()
|
||||
const { projectV2: project } = injectProjectPageContext()
|
||||
const { labrinth } = injectModrinthClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const queryClient = useQueryClient()
|
||||
const deleteAllGroupsModalRef =
|
||||
useTemplateRef<InstanceType<typeof ConfirmModal>>('deleteAllGroupsModalRef')
|
||||
|
||||
type SortType = 'status' | 'most_files' | 'recently_edited' | 'rejected'
|
||||
|
||||
@@ -369,6 +377,46 @@ const messages = defineMessages({
|
||||
defaultMessage:
|
||||
'{count, plural, =0 {No attributions need approval} one {# attribution needs approval} other {# attributions need approval}}',
|
||||
},
|
||||
deleteAllGroups: {
|
||||
id: 'project.settings.permissions.delete-all-groups',
|
||||
defaultMessage: 'Delete all groups',
|
||||
},
|
||||
deleteAllGroupsConfirmationTitle: {
|
||||
id: 'project.settings.permissions.delete-all-groups-confirmation.title',
|
||||
defaultMessage: 'Delete all attribution groups?',
|
||||
},
|
||||
deleteAllGroupsConfirmationDescription: {
|
||||
id: 'project.settings.permissions.delete-all-groups-confirmation.description',
|
||||
defaultMessage:
|
||||
'This will permanently delete all {count, plural, one {# attribution group} other {# attribution groups}} for this project and all files inside them. This action cannot be undone.',
|
||||
},
|
||||
deleteAllGroupsSuccess: {
|
||||
id: 'project.settings.permissions.delete-all-groups-success',
|
||||
defaultMessage: 'All attribution groups were deleted.',
|
||||
},
|
||||
deleteAllGroupsError: {
|
||||
id: 'project.settings.permissions.delete-all-groups-error',
|
||||
defaultMessage: 'Could not delete all attribution groups',
|
||||
},
|
||||
})
|
||||
|
||||
const deleteAllGroupsMutation = useMutation({
|
||||
mutationFn: () => labrinth.attribution_internal.deleteAllGroups(project.value.id),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['project-attribution', project.value.id] })
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: formatMessage(messages.deleteAllGroups),
|
||||
text: formatMessage(messages.deleteAllGroupsSuccess),
|
||||
})
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: formatMessage(messages.deleteAllGroupsError),
|
||||
text: error.message,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function defaultCardCollapsed(group: Labrinth.Attribution.Internal.AttributionGroup): boolean {
|
||||
@@ -412,12 +460,26 @@ function toggleAllCardsCollapsed() {
|
||||
cardCollapsedById.value = next
|
||||
}
|
||||
|
||||
function showDeleteAllGroupsConfirmation() {
|
||||
deleteAllGroupsModalRef.value?.show()
|
||||
}
|
||||
|
||||
function dismissInfoBanner() {
|
||||
flags.value.dismissedExternalProjectsInfo = true
|
||||
saveFeatureFlags()
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<ConfirmModal
|
||||
v-if="isModerator"
|
||||
ref="deleteAllGroupsModalRef"
|
||||
:title="formatMessage(messages.deleteAllGroupsConfirmationTitle)"
|
||||
:description="
|
||||
formatMessage(messages.deleteAllGroupsConfirmationDescription, { count: totalGroups })
|
||||
"
|
||||
:proceed-label="formatMessage(messages.deleteAllGroups)"
|
||||
@proceed="deleteAllGroupsMutation.mutate()"
|
||||
/>
|
||||
<Admonition
|
||||
v-if="!flags.dismissedExternalProjectsInfo && !isModerator"
|
||||
type="info"
|
||||
@@ -500,7 +562,7 @@ function dismissInfoBanner() {
|
||||
/>
|
||||
</template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="grid grid-cols-[1fr_auto_auto] gap-2">
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-[minmax(0,1fr)_auto_auto_auto]">
|
||||
<StyledInput
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
@@ -554,6 +616,21 @@ function dismissInfoBanner() {
|
||||
{{ expandCollapseAllLabel }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="isModerator" color="red" type="outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="!h-[40px]"
|
||||
:disabled="deleteAllGroupsMutation.isPending.value"
|
||||
@click="showDeleteAllGroupsConfirmation"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="deleteAllGroupsMutation.isPending.value"
|
||||
class="size-5 flex-shrink-0 animate-spin"
|
||||
/>
|
||||
<TrashIcon v-else class="size-5 flex-shrink-0" />
|
||||
{{ formatMessage(messages.deleteAllGroups) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isModerator" class="mt-4 flex flex-wrap items-center gap-3">
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tDELETE FROM project_attribution_files\n\t\tWHERE group_id = $1\n\t\t",
|
||||
"query": "\n\t\tDELETE FROM project_attribution_files\n\t\tWHERE group_id = ANY($1)\n\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b69317d234e934aa6bdf8be4d35bc7475308bde8b7f34a4ef3c7f3ef351f46a8"
|
||||
"hash": "14779c6105c15c6a117332caf850c7939af5d70562eb309680047da3c76f74fd"
|
||||
}
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tDELETE FROM project_attribution_groups\n\t\tWHERE id = $1\n\t\t",
|
||||
"query": "\n\t\tDELETE FROM project_attribution_groups\n\t\tWHERE id = ANY($1)\n\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "827534d191739cd2ea502142a65f62a052715c7441952ee4ee182afde07c44d7"
|
||||
"hash": "41ec69f790366c741cea01aa7e1bfac7912b4ed17b7d19b5ba10221fa93bc3f8"
|
||||
}
|
||||
Generated
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tSELECT id AS \"id: DBAttributionGroupId\"\n\t\tFROM project_attribution_groups\n\t\tWHERE project_id = $1\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: DBAttributionGroupId",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "78f66d65d665210fd8334d2e1e8a793ce8128f7d6b53b3b408b030bfee4b3841"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tSELECT DISTINCT f.version_id AS \"version_id: DBVersionId\"\n\t\tFROM project_attribution_files paf\n\t\tINNER JOIN project_attribution_groups pag ON pag.id = paf.group_id\n\t\tINNER JOIN override_file_sources ofs ON ofs.sha1 = paf.sha1\n\t\tINNER JOIN files f ON f.id = ofs.file_id\n\t\tINNER JOIN versions v ON v.id = f.version_id\n\t\tWHERE paf.group_id = $1\n\t\t\tAND pag.project_id = v.mod_id\n\t\t",
|
||||
"query": "\n\t\tSELECT DISTINCT f.version_id AS \"version_id: DBVersionId\"\n\t\tFROM project_attribution_files paf\n\t\tINNER JOIN project_attribution_groups pag ON pag.id = paf.group_id\n\t\tINNER JOIN override_file_sources ofs ON ofs.sha1 = paf.sha1\n\t\tINNER JOIN files f ON f.id = ofs.file_id\n\t\tINNER JOIN versions v ON v.id = f.version_id\n\t\tWHERE paf.group_id = ANY($1)\n\t\t\tAND pag.project_id = v.mod_id\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -11,12 +11,12 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "00ef039799c162e21a8fa5ba7aec2a9044a3028f1cb34e8c9c565a670956d938"
|
||||
"hash": "bfc67441eb246ac6b155db36919a7a62e19e0dd0c87916e761175ec909add15b"
|
||||
}
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET email_verified = TRUE WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cb626a36deffd73e67de2dc4789bd875675779a173fb2cd41ba365c1acca96f9"
|
||||
}
|
||||
@@ -14,7 +14,7 @@ use crate::database::models::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::{FileId, ProjectId, VersionId};
|
||||
use crate::models::ids::{AttributionGroupId, FileId, ProjectId, VersionId};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::{
|
||||
AttributionModerationStatusKind, AttributionResolution,
|
||||
@@ -31,7 +31,8 @@ use crate::util::error::Context;
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(list)
|
||||
.service(update_group)
|
||||
.service(delete_group)
|
||||
.service(delete_groups)
|
||||
.service(delete_all_groups)
|
||||
.service(scan)
|
||||
.service(force_scan_file)
|
||||
.service(assign)
|
||||
@@ -706,19 +707,25 @@ pub async fn update_group(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete an attribution group and all files inside it.
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
struct DeleteGroupsBody {
|
||||
groups: Vec<AttributionGroupId>,
|
||||
}
|
||||
|
||||
/// Delete attribution groups and all files inside them.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
request_body = DeleteGroupsBody,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/group/{group_id}")]
|
||||
pub async fn delete_group(
|
||||
#[delete("/group")]
|
||||
pub async fn delete_groups(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
path: web::Path<i64>,
|
||||
web::Json(body): web::Json<DeleteGroupsBody>,
|
||||
) -> Result<(), ApiError> {
|
||||
check_is_moderator_from_headers(
|
||||
&req,
|
||||
@@ -729,11 +736,79 @@ pub async fn delete_group(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let group_ids = body
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(|id| DBAttributionGroupId::from(id).0)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
delete_attribution_groups(pool.as_ref(), redis.as_ref(), group_ids).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
struct DeleteAllGroupsBody {
|
||||
project_id: ProjectId,
|
||||
}
|
||||
|
||||
/// Delete all attribution groups and files for a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
request_body = DeleteAllGroupsBody,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/all-groups")]
|
||||
pub async fn delete_all_groups(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
web::Json(body): web::Json<DeleteAllGroupsBody>,
|
||||
) -> Result<(), ApiError> {
|
||||
check_is_moderator_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Scopes::PROJECT_READ,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let project_id = DBProjectId::from(body.project_id).0;
|
||||
let group_ids = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT id AS "id: DBAttributionGroupId"
|
||||
FROM project_attribution_groups
|
||||
WHERE project_id = $1
|
||||
"#,
|
||||
project_id,
|
||||
)
|
||||
.fetch_all(pool.as_ref())
|
||||
.await
|
||||
.wrap_internal_err("failed to fetch project attribution groups")?
|
||||
.into_iter()
|
||||
.map(|id| id.0)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
delete_attribution_groups(pool.as_ref(), redis.as_ref(), group_ids).await
|
||||
}
|
||||
|
||||
async fn delete_attribution_groups(
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
mut group_ids: Vec<i64>,
|
||||
) -> Result<(), ApiError> {
|
||||
group_ids.sort_unstable();
|
||||
group_ids.dedup();
|
||||
|
||||
if group_ids.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut txn = pool.begin().await.wrap_internal_err(
|
||||
"failed to begin attribution group deletion transaction",
|
||||
)?;
|
||||
|
||||
let group_id = path.into_inner();
|
||||
let version_ids = sqlx::query_scalar!(
|
||||
r#"
|
||||
SELECT DISTINCT f.version_id AS "version_id: DBVersionId"
|
||||
@@ -742,10 +817,10 @@ pub async fn delete_group(
|
||||
INNER JOIN override_file_sources ofs ON ofs.sha1 = paf.sha1
|
||||
INNER JOIN files f ON f.id = ofs.file_id
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE paf.group_id = $1
|
||||
WHERE paf.group_id = ANY($1)
|
||||
AND pag.project_id = v.mod_id
|
||||
"#,
|
||||
group_id,
|
||||
&group_ids,
|
||||
)
|
||||
.fetch_all(&mut txn)
|
||||
.await
|
||||
@@ -754,9 +829,9 @@ pub async fn delete_group(
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM project_attribution_files
|
||||
WHERE group_id = $1
|
||||
WHERE group_id = ANY($1)
|
||||
",
|
||||
group_id,
|
||||
&group_ids,
|
||||
)
|
||||
.execute(&mut txn)
|
||||
.await
|
||||
@@ -765,15 +840,15 @@ pub async fn delete_group(
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
DELETE FROM project_attribution_groups
|
||||
WHERE id = $1
|
||||
WHERE id = ANY($1)
|
||||
",
|
||||
group_id,
|
||||
&group_ids,
|
||||
)
|
||||
.execute(&mut txn)
|
||||
.await
|
||||
.wrap_internal_err("failed to delete attribution group")?;
|
||||
.wrap_internal_err("failed to delete attribution groups")?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
if result.rows_affected() != group_ids.len() as u64 {
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
@@ -781,7 +856,7 @@ pub async fn delete_group(
|
||||
"failed to commit attribution group deletion transaction",
|
||||
)?;
|
||||
|
||||
DBVersion::clear_cache_ids(&version_ids, redis.as_ref())
|
||||
DBVersion::clear_cache_ids(&version_ids, redis)
|
||||
.await
|
||||
.wrap_internal_err("failed to clear version attribution cache")?;
|
||||
|
||||
|
||||
@@ -142,7 +142,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
attribution::scan,
|
||||
attribution::list,
|
||||
attribution::update_group,
|
||||
attribution::delete_group,
|
||||
attribution::delete_groups,
|
||||
attribution::delete_all_groups,
|
||||
attribution::assign,
|
||||
attribution::split,
|
||||
billing::products,
|
||||
|
||||
@@ -65,17 +65,34 @@ export class LabrinthAttributionInternalModule extends AbstractModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an attribution group and all files inside it.
|
||||
* DELETE /_internal/attribution/group/{group_id}
|
||||
* Delete attribution groups and all files inside them.
|
||||
* DELETE /_internal/attribution/group
|
||||
*
|
||||
* @param groupId - The base62 attribution group id (as returned from listProjectAttribution).
|
||||
* @param groupIds - The base62 attribution group ids (as returned from listProjectAttribution).
|
||||
*/
|
||||
public async deleteGroup(groupId: string): Promise<void> {
|
||||
const numericId = decodeBase62Id(groupId)
|
||||
return this.client.request<void>(`/attribution/group/${numericId}`, {
|
||||
public async deleteGroups(groupIds: string[]): Promise<void> {
|
||||
const body: Labrinth.Attribution.Internal.DeleteGroupsRequest = { groups: groupIds }
|
||||
return this.client.request<void>('/attribution/group', {
|
||||
api: 'labrinth',
|
||||
version: 'internal',
|
||||
method: 'DELETE',
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all attribution groups and files for a project.
|
||||
* DELETE /_internal/attribution/all-groups
|
||||
*/
|
||||
public async deleteAllGroups(projectId: string): Promise<void> {
|
||||
const body: Labrinth.Attribution.Internal.DeleteAllGroupsRequest = {
|
||||
project_id: projectId,
|
||||
}
|
||||
return this.client.request<void>('/attribution/all-groups', {
|
||||
api: 'labrinth',
|
||||
version: 'internal',
|
||||
method: 'DELETE',
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +397,14 @@ export namespace Labrinth {
|
||||
attribution: AttributionResolution
|
||||
}
|
||||
|
||||
export type DeleteGroupsRequest = {
|
||||
groups: string[]
|
||||
}
|
||||
|
||||
export type DeleteAllGroupsRequest = {
|
||||
project_id: string
|
||||
}
|
||||
|
||||
export type AssignRequest = {
|
||||
sha1: string
|
||||
target_group_id: number
|
||||
|
||||
@@ -18,7 +18,7 @@ import { renderString } from '@modrinth/utils'
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import { ButtonStyled, Collapsible, OverflowMenu } from '#ui/components'
|
||||
import { ButtonStyled, Collapsible, ConfirmModal, OverflowMenu } from '#ui/components'
|
||||
import type { OverflowMenuOption } from '#ui/components/base'
|
||||
import { commonMessages } from '#ui/utils'
|
||||
|
||||
@@ -79,6 +79,7 @@ const addToGlobalModalRef =
|
||||
useTemplateRef<typeof AddToGlobalPermissionsDatabaseModal>('addToGlobalModalRef')
|
||||
const addToExistingModalRef =
|
||||
useTemplateRef<typeof AddToExistingExternalProjectModal>('addToExistingModalRef')
|
||||
const deleteGroupModalRef = useTemplateRef<InstanceType<typeof ConfirmModal>>('deleteGroupModalRef')
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const client = injectModrinthClient()
|
||||
@@ -138,7 +139,20 @@ const messages = defineMessages({
|
||||
},
|
||||
removeGroup: {
|
||||
id: 'external-files.permissions-card.remove-group',
|
||||
defaultMessage: 'Remove group',
|
||||
defaultMessage: 'Delete group',
|
||||
},
|
||||
removeGroupConfirmationTitle: {
|
||||
id: 'external-files.permissions-card.remove-group-confirmation.title',
|
||||
defaultMessage: 'Delete {title}?',
|
||||
},
|
||||
removeGroupConfirmationDescription: {
|
||||
id: 'external-files.permissions-card.remove-group-confirmation.description',
|
||||
defaultMessage:
|
||||
'This will permanently delete this attribution group and all files inside it. This action cannot be undone.',
|
||||
},
|
||||
removeGroupShiftHint: {
|
||||
id: 'external-files.permissions-card.remove-group-shift-hint',
|
||||
defaultMessage: 'Hold Shift while clicking to skip confirmation.',
|
||||
},
|
||||
moderationReasonLabel: {
|
||||
id: 'external-files.permissions-card.moderation-reason',
|
||||
@@ -254,7 +268,7 @@ const splitFileMutation = useMutation({
|
||||
})
|
||||
|
||||
const deleteGroupMutation = useMutation({
|
||||
mutationFn: () => client.labrinth.attribution_internal.deleteGroup(props.group.id),
|
||||
mutationFn: () => client.labrinth.attribution_internal.deleteGroups([props.group.id]),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['project-attribution', props.projectId] })
|
||||
emit('updated')
|
||||
@@ -265,7 +279,7 @@ const deleteGroupMutation = useMutation({
|
||||
title: formatMessage(
|
||||
defineMessage({
|
||||
id: 'external-files.permissions-card.remove-group-error.title',
|
||||
defaultMessage: 'Could not remove group',
|
||||
defaultMessage: 'Could not delete group',
|
||||
}),
|
||||
),
|
||||
text: error.message,
|
||||
@@ -305,10 +319,19 @@ function handleConfirmAddFiles(sha1s: string[]) {
|
||||
assignFilesMutation.mutate(sha1s)
|
||||
}
|
||||
|
||||
function handleDeleteGroup() {
|
||||
function deleteGroup() {
|
||||
deleteGroupMutation.mutate()
|
||||
}
|
||||
|
||||
function handleDeleteGroup(event: MouseEvent) {
|
||||
if (event.shiftKey) {
|
||||
deleteGroup()
|
||||
return
|
||||
}
|
||||
|
||||
deleteGroupModalRef.value?.show()
|
||||
}
|
||||
|
||||
async function handleAddFilesToGroup(event: MouseEvent) {
|
||||
try {
|
||||
const groups = await queryClient.ensureQueryData({
|
||||
@@ -825,6 +848,18 @@ const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible>
|
||||
<ConfirmModal
|
||||
v-if="isModerator"
|
||||
ref="deleteGroupModalRef"
|
||||
:title="formatMessage(messages.removeGroupConfirmationTitle, { title })"
|
||||
:description="formatMessage(messages.removeGroupConfirmationDescription)"
|
||||
:proceed-label="formatMessage(messages.removeGroup)"
|
||||
@proceed="deleteGroup"
|
||||
>
|
||||
<p class="m-0 text-xs text-secondary">
|
||||
{{ formatMessage(messages.removeGroupShiftHint) }}
|
||||
</p>
|
||||
</ConfirmModal>
|
||||
<AddFilesToAttributionGroupModal
|
||||
ref="addFilesModalRef"
|
||||
:group-id="group.id"
|
||||
|
||||
@@ -1170,10 +1170,19 @@
|
||||
"defaultMessage": "You have obtained special permission to redistribute this work in your modpack."
|
||||
},
|
||||
"external-files.permissions-card.remove-group": {
|
||||
"defaultMessage": "Remove group"
|
||||
"defaultMessage": "Delete group"
|
||||
},
|
||||
"external-files.permissions-card.remove-group-confirmation.description": {
|
||||
"defaultMessage": "This will permanently delete this attribution group and all files inside it. This action cannot be undone."
|
||||
},
|
||||
"external-files.permissions-card.remove-group-confirmation.title": {
|
||||
"defaultMessage": "Delete {title}?"
|
||||
},
|
||||
"external-files.permissions-card.remove-group-error.title": {
|
||||
"defaultMessage": "Could not remove group"
|
||||
"defaultMessage": "Could not delete group"
|
||||
},
|
||||
"external-files.permissions-card.remove-group-shift-hint": {
|
||||
"defaultMessage": "Hold Shift while clicking to skip confirmation."
|
||||
},
|
||||
"external-files.permissions-card.split-file": {
|
||||
"defaultMessage": "Remove from group"
|
||||
|
||||
Reference in New Issue
Block a user