Add TailwindCSS (#1252)

* Setup TailwindCSS

* Fully setup configuration

* Refactor some tailwind variables
This commit is contained in:
Evan Song
2024-07-06 20:57:32 -07:00
committed by GitHub
parent 0f2ddb452c
commit abec2e48d4
176 changed files with 7905 additions and 7433 deletions

View File

@@ -153,7 +153,7 @@
return {
label: formatMessage(getProjectTypeMessage(x, true)),
href: `/organization/${organization.slug}/${x}s`,
}
};
}),
]"
/>
@@ -170,8 +170,8 @@
v-for="project in (route.params.projectType !== undefined
? projects.filter((x) =>
x.project_types.includes(
route.params.projectType.substr(0, route.params.projectType.length - 1)
)
route.params.projectType.substr(0, route.params.projectType.length - 1),
),
)
: projects
)
@@ -227,42 +227,42 @@ import {
ChartIcon,
CheckIcon,
XIcon,
} from '@modrinth/assets'
import { Avatar, Breadcrumbs, Promotion } from '@modrinth/ui'
import NavStack from '~/components/ui/NavStack.vue'
import NavStackItem from '~/components/ui/NavStackItem.vue'
import NavRow from '~/components/ui/NavRow.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue'
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
import ProjectCard from '~/components/ui/ProjectCard.vue'
} from "@modrinth/assets";
import { Avatar, Breadcrumbs, Promotion } from "@modrinth/ui";
import NavStack from "~/components/ui/NavStack.vue";
import NavStackItem from "~/components/ui/NavStackItem.vue";
import NavRow from "~/components/ui/NavRow.vue";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
import ProjectCard from "~/components/ui/ProjectCard.vue";
import OrganizationIcon from '~/assets/images/utils/organization.svg?component'
import DownloadIcon from '~/assets/images/utils/download.svg?component'
import CrownIcon from '~/assets/images/utils/crown.svg?component'
import { acceptTeamInvite, removeTeamMember } from '~/helpers/teams.js'
import OrganizationIcon from "~/assets/images/utils/organization.svg?component";
import DownloadIcon from "~/assets/images/utils/download.svg?component";
import CrownIcon from "~/assets/images/utils/crown.svg?component";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
const vintl = useVIntl()
const { formatMessage } = vintl
const vintl = useVIntl();
const { formatMessage } = vintl;
const formatCompactNumber = useCompactNumber()
const formatCompactNumber = useCompactNumber();
const auth = await useAuth()
const user = await useUser()
const cosmetics = useCosmetics()
const route = useNativeRoute()
const tags = useTags()
const auth = await useAuth();
const user = await useUser();
const cosmetics = useCosmetics();
const route = useNativeRoute();
const tags = useTags();
let orgId = useRouteId()
let orgId = useRouteId();
// hacky way to show the edit button on the corner of the card.
const routeHasSettings = computed(() => route.path.includes('settings'))
const routeHasSettings = computed(() => route.path.includes("settings"));
const [
{ data: organization, refresh: refreshOrganization },
{ data: projects, refresh: refreshProjects },
] = await Promise.all([
useAsyncData(`organization/${orgId}`, () =>
useBaseFetch(`organization/${orgId}`, { apiVersion: 3 })
useBaseFetch(`organization/${orgId}`, { apiVersion: 3 }),
),
useAsyncData(
`organization/${orgId}/projects`,
@@ -270,162 +270,162 @@ const [
{
transform: (projects) => {
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
project.categories = project.categories.concat(project.loaders);
if (project.mrpack_loaders) {
project.categories = project.categories.concat(project.mrpack_loaders)
project.categories = project.categories.concat(project.mrpack_loaders);
}
const singleplayer = project.singleplayer && project.singleplayer[0]
const clientAndServer = project.client_and_server && project.client_and_server[0]
const clientOnly = project.client_only && project.client_only[0]
const serverOnly = project.server_only && project.server_only[0]
const singleplayer = project.singleplayer && project.singleplayer[0];
const clientAndServer = project.client_and_server && project.client_and_server[0];
const clientOnly = project.client_only && project.client_only[0];
const serverOnly = project.server_only && project.server_only[0];
// quick and dirty hack to show envs as legacy
if (singleplayer && clientAndServer && !clientOnly && !serverOnly) {
project.client_side = 'required'
project.server_side = 'required'
project.client_side = "required";
project.server_side = "required";
} else if (singleplayer && clientAndServer && clientOnly && !serverOnly) {
project.client_side = 'required'
project.server_side = 'unsupported'
project.client_side = "required";
project.server_side = "unsupported";
} else if (singleplayer && clientAndServer && !clientOnly && serverOnly) {
project.client_side = 'unsupported'
project.server_side = 'required'
project.client_side = "unsupported";
project.server_side = "required";
} else if (singleplayer && clientAndServer && clientOnly && serverOnly) {
project.client_side = 'optional'
project.server_side = 'optional'
project.client_side = "optional";
project.server_side = "optional";
}
}
return projects
return projects;
},
}
},
),
])
]);
const refresh = async () => {
await Promise.all([refreshOrganization(), refreshProjects()])
}
await Promise.all([refreshOrganization(), refreshProjects()]);
};
if (!organization.value) {
throw createError({
fatal: true,
statusCode: 404,
message: 'Organization not found',
})
message: "Organization not found",
});
}
// Filter accepted, sort by role, then by name and Owner role always goes first
const acceptedMembers = computed(() => {
const acceptedMembers = organization.value.members?.filter((x) => x.accepted)
const owner = acceptedMembers.find((x) => x.is_owner)
const rest = acceptedMembers.filter((x) => !x.is_owner) || []
const acceptedMembers = organization.value.members?.filter((x) => x.accepted);
const owner = acceptedMembers.find((x) => x.is_owner);
const rest = acceptedMembers.filter((x) => !x.is_owner) || [];
rest.sort((a, b) => {
if (a.role === b.role) {
return a.user.username.localeCompare(b.user.username)
return a.user.username.localeCompare(b.user.username);
} else {
return a.role.localeCompare(b.role)
return a.role.localeCompare(b.role);
}
})
});
return [owner, ...rest]
})
return [owner, ...rest];
});
const currentMember = computed(() => {
if (auth.value.user && organization.value) {
const member = organization.value.members.find((x) => x.user.id === auth.value.user.id)
const member = organization.value.members.find((x) => x.user.id === auth.value.user.id);
if (member) {
return member
return member;
}
if (tags.value.staffRoles.includes(auth.value.user.role)) {
return {
user: auth.value.user,
role: auth.value.user.role,
permissions: auth.value.user.role === 'admin' ? 1023 : 12,
permissions: auth.value.user.role === "admin" ? 1023 : 12,
accepted: true,
payouts_split: 0,
avatar_url: auth.value.user.avatar_url,
name: auth.value.user.username,
}
};
}
}
return null
})
return null;
});
const hasPermission = computed(() => {
const EDIT_DETAILS = 1 << 2
return currentMember.value && (currentMember.value.permissions & EDIT_DETAILS) === EDIT_DETAILS
})
const EDIT_DETAILS = 1 << 2;
return currentMember.value && (currentMember.value.permissions & EDIT_DETAILS) === EDIT_DETAILS;
});
const isInvited = computed(() => {
return currentMember.value?.accepted === false
})
return currentMember.value?.accepted === false;
});
const projectTypes = computed(() => {
const obj = {}
const obj = {};
for (const project of projects.value) {
obj[project.project_types[0] ?? 'project'] = true
obj[project.project_types[0] ?? "project"] = true;
}
delete obj.project
delete obj.project;
return Object.keys(obj)
})
return Object.keys(obj);
});
const sumDownloads = computed(() => {
let sum = 0
let sum = 0;
for (const project of projects.value) {
sum += project.downloads
sum += project.downloads;
}
return sum
})
return sum;
});
const patchIcon = async (icon) => {
const ext = icon.name.split('.').pop()
const ext = icon.name.split(".").pop();
await useBaseFetch(`organization/${organization.value.id}/icon`, {
method: 'PATCH',
method: "PATCH",
body: icon,
query: { ext },
apiVersion: 3,
})
}
});
};
const deleteIcon = async () => {
await useBaseFetch(`organization/${organization.value.id}/icon`, {
method: 'DELETE',
method: "DELETE",
apiVersion: 3,
})
}
});
};
const patchOrganization = async (id, newData) => {
await useBaseFetch(`organization/${id}`, {
method: 'PATCH',
method: "PATCH",
body: newData,
apiVersion: 3,
})
});
if (newData.slug) {
orgId = newData.slug
orgId = newData.slug;
}
}
};
const onAcceptInvite = useClientTry(async () => {
await acceptTeamInvite(organization.value.team_id)
await refreshOrganization()
})
await acceptTeamInvite(organization.value.team_id);
await refreshOrganization();
});
const onDeclineInvite = useClientTry(async () => {
await removeTeamMember(organization.value.team_id, auth.value?.user.id)
await refreshOrganization()
})
await removeTeamMember(organization.value.team_id, auth.value?.user.id);
await refreshOrganization();
});
provide('organizationContext', {
provide("organizationContext", {
organization,
projects,
refresh,
@@ -434,18 +434,18 @@ provide('organizationContext', {
patchIcon,
deleteIcon,
patchOrganization,
})
});
const title = `${organization.value.name} - Organization`
const description = `${organization.value.description} - View the organization ${organization.value.name} on Modrinth`
const title = `${organization.value.name} - Organization`;
const description = `${organization.value.description} - View the organization ${organization.value.name} on Modrinth`;
useSeoMeta({
title,
description,
ogTitle: title,
ogDescription: organization.value.description,
ogImage: organization.value.icon_url ?? 'https://cdn.modrinth.com/placeholder.png',
})
ogImage: organization.value.icon_url ?? "https://cdn.modrinth.com/placeholder.png",
});
</script>
<style scoped lang="scss">
@@ -493,8 +493,8 @@ useSeoMeta({
margin-left: -0.5rem;
border-radius: var(--radius-lg);
grid-template:
'avatar name' auto
'avatar role' auto
"avatar name" auto
"avatar role" auto
/ auto 1fr;
p {
margin: 0;

View File

@@ -15,9 +15,9 @@
</template>
<script setup>
import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue'
import ChartDisplay from "~/components/ui/charts/ChartDisplay.vue";
const { projects } = inject('organizationContext')
const { projects } = inject("organizationContext");
</script>
<style scoped lang="scss">

View File

@@ -1,6 +1,6 @@
<script setup>
import { Button, FileInput, Avatar, ConfirmModal } from '@modrinth/ui'
import { UploadIcon, SaveIcon, TrashIcon } from '@modrinth/assets'
import { Button, FileInput, Avatar, ConfirmModal } from "@modrinth/ui";
import { UploadIcon, SaveIcon, TrashIcon } from "@modrinth/assets";
const {
organization,
@@ -9,93 +9,93 @@ const {
deleteIcon,
patchIcon,
patchOrganization,
} = inject('organizationContext')
} = inject("organizationContext");
const icon = ref(null)
const deletedIcon = ref(false)
const previewImage = ref(null)
const icon = ref(null);
const deletedIcon = ref(false);
const previewImage = ref(null);
const name = ref(organization.value.name)
const slug = ref(organization.value.slug)
const name = ref(organization.value.name);
const slug = ref(organization.value.slug);
const summary = ref(organization.value.description)
const summary = ref(organization.value.description);
const patchData = computed(() => {
const data = {}
const data = {};
if (name.value !== organization.value.name) {
data.name = name.value
data.name = name.value;
}
if (slug.value !== organization.value.slug) {
data.slug = slug.value
data.slug = slug.value;
}
if (summary.value !== organization.value.description) {
data.description = summary.value
data.description = summary.value;
}
return data
})
return data;
});
const hasChanges = computed(() => {
return Object.keys(patchData.value).length > 0 || deletedIcon.value || icon.value
})
return Object.keys(patchData.value).length > 0 || deletedIcon.value || icon.value;
});
const markIconForDeletion = () => {
deletedIcon.value = true
icon.value = null
previewImage.value = null
}
deletedIcon.value = true;
icon.value = null;
previewImage.value = null;
};
const showPreviewImage = (files) => {
const reader = new FileReader()
const reader = new FileReader();
icon.value = files[0]
deletedIcon.value = false
icon.value = files[0];
deletedIcon.value = false;
reader.readAsDataURL(icon.value)
reader.readAsDataURL(icon.value);
reader.onload = (event) => {
previewImage.value = event.target.result
}
}
previewImage.value = event.target.result;
};
};
const orgId = useRouteId()
const orgId = useRouteId();
const onSaveChanges = useClientTry(async () => {
if (hasChanges.value) {
await patchOrganization(orgId, patchData.value)
await patchOrganization(orgId, patchData.value);
}
if (deletedIcon.value) {
await deleteIcon()
deletedIcon.value = false
await deleteIcon();
deletedIcon.value = false;
} else if (icon.value) {
await patchIcon(icon.value)
icon.value = null
await patchIcon(icon.value);
icon.value = null;
}
await refreshOrganization()
await refreshOrganization();
addNotification({
group: 'main',
title: 'Organization updated',
text: 'Your organization has been updated.',
type: 'success',
})
})
group: "main",
title: "Organization updated",
text: "Your organization has been updated.",
type: "success",
});
});
const onDeleteOrganization = useClientTry(async () => {
await useBaseFetch(`organization/${orgId}`, {
method: 'DELETE',
method: "DELETE",
apiVersion: 3,
})
});
addNotification({
group: 'main',
title: 'Organization deleted',
text: 'Your organization has been deleted.',
type: 'success',
})
group: "main",
title: "Organization deleted",
text: "Your organization has been deleted.",
type: "success",
});
await navigateTo('/dashboard/organizations')
})
await navigateTo("/dashboard/organizations");
});
</script>
<template>

View File

@@ -22,7 +22,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.MANAGE_INVITES
organizationPermissions.MANAGE_INVITES,
)
"
@keypress.enter="() => onInviteTeamMember(organization.team, currentUsername)"
@@ -33,7 +33,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.MANAGE_INVITES
organizationPermissions.MANAGE_INVITES,
)
"
@click="() => onInviteTeamMember(organization.team_id, currentUsername)"
@@ -108,7 +108,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER
organizationPermissions.EDIT_MEMBER,
)
"
/>
@@ -128,7 +128,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER
organizationPermissions.EDIT_MEMBER,
)
"
/>
@@ -145,7 +145,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER_DEFAULT_PERMISSIONS
organizationPermissions.EDIT_MEMBER_DEFAULT_PERMISSIONS,
) || !isPermission(currentMember.permissions, permission)
"
:label="permToLabel(label)"
@@ -165,7 +165,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER
organizationPermissions.EDIT_MEMBER,
) || !isPermission(currentMember.organization_permissions, permission)
"
:label="permToLabel(label)"
@@ -179,7 +179,7 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER
organizationPermissions.EDIT_MEMBER,
)
"
@click="onUpdateTeamMember(organization.team_id, member)"
@@ -193,11 +193,11 @@
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER
organizationPermissions.EDIT_MEMBER,
) &&
!isPermission(
currentMember.organization_permissions,
organizationPermissions.REMOVE_MEMBER
organizationPermissions.REMOVE_MEMBER,
)
"
@click="onRemoveMember(organization.team_id, member)"
@@ -225,29 +225,29 @@ import {
UserPlusIcon,
UserXIcon as UserRemoveIcon,
DropdownIcon,
} from '@modrinth/assets'
import { Button, Badge, Avatar, Checkbox } from '@modrinth/ui'
import { ref } from 'vue'
import CrownIcon from '~/assets/images/utils/crown.svg?component'
} from "@modrinth/assets";
import { Button, Badge, Avatar, Checkbox } from "@modrinth/ui";
import { ref } from "vue";
import CrownIcon from "~/assets/images/utils/crown.svg?component";
import { removeTeamMember } from '~/helpers/teams.js'
import { isPermission } from '~/utils/permissions.ts'
import { removeTeamMember } from "~/helpers/teams.js";
import { isPermission } from "~/utils/permissions.ts";
const { organization, refresh: refreshOrganization, currentMember } = inject('organizationContext')
const { organization, refresh: refreshOrganization, currentMember } = inject("organizationContext");
const auth = await useAuth()
const auth = await useAuth();
const currentUsername = ref('')
const openTeamMembers = ref([])
const currentUsername = ref("");
const openTeamMembers = ref([]);
const allTeamMembers = ref(organization.value.members)
const allTeamMembers = ref(organization.value.members);
watch(
() => organization.value,
() => {
allTeamMembers.value = organization.value.members
}
)
allTeamMembers.value = organization.value.members;
},
);
const projectPermissions = {
UPLOAD_VERSION: 1 << 0,
@@ -260,7 +260,7 @@ const projectPermissions = {
DELETE_PROJECT: 1 << 7,
VIEW_ANALYTICS: 1 << 8,
VIEW_PAYOUTS: 1 << 9,
}
};
const organizationPermissions = {
EDIT_DETAILS: 1 << 0,
@@ -271,49 +271,49 @@ const organizationPermissions = {
REMOVE_PROJECT: 1 << 5,
DELETE_ORGANIZATION: 1 << 6,
EDIT_MEMBER_DEFAULT_PERMISSIONS: 1 << 7,
}
};
const permToLabel = (key) => {
const o = key.split('_').join(' ')
return o.charAt(0).toUpperCase() + o.slice(1).toLowerCase()
}
const o = key.split("_").join(" ");
return o.charAt(0).toUpperCase() + o.slice(1).toLowerCase();
};
const leaveProject = async (teamId, uid) => {
await removeTeamMember(teamId, uid)
await navigateTo(`/organization/${organization.value.id}`)
}
await removeTeamMember(teamId, uid);
await navigateTo(`/organization/${organization.value.id}`);
};
const onLeaveProject = useClientTry(leaveProject)
const onLeaveProject = useClientTry(leaveProject);
const onInviteTeamMember = useClientTry(async (teamId, username) => {
const user = await useBaseFetch(`user/${username}`)
const user = await useBaseFetch(`user/${username}`);
const data = {
user_id: user.id.trim(),
}
};
await useBaseFetch(`team/${teamId}/members`, {
method: 'POST',
method: "POST",
body: data,
})
await refreshOrganization()
currentUsername.value = ''
});
await refreshOrganization();
currentUsername.value = "";
addNotification({
group: 'main',
title: 'Member invited',
group: "main",
title: "Member invited",
text: `${user.username} has been invited to the organization.`,
type: 'success',
})
})
type: "success",
});
});
const onRemoveMember = useClientTry(async (teamId, member) => {
await removeTeamMember(teamId, member.user.id)
await refreshOrganization()
await removeTeamMember(teamId, member.user.id);
await refreshOrganization();
addNotification({
group: 'main',
title: 'Member removed',
group: "main",
title: "Member removed",
text: `${member.user.username} has been removed from the organization.`,
type: 'success',
})
})
type: "success",
});
});
const onUpdateTeamMember = useClientTry(async (teamId, member) => {
const data = !member.is_owner
@@ -326,36 +326,36 @@ const onUpdateTeamMember = useClientTry(async (teamId, member) => {
: {
payouts_split: member.payouts_split,
role: member.role,
}
};
await useBaseFetch(`team/${teamId}/members/${member.user.id}`, {
method: 'PATCH',
method: "PATCH",
body: data,
})
await refreshOrganization()
});
await refreshOrganization();
addNotification({
group: 'main',
title: 'Member updated',
group: "main",
title: "Member updated",
text: `${member.user.username} has been updated.`,
type: 'success',
})
})
type: "success",
});
});
const onTransferOwnership = useClientTry(async (teamId, uid) => {
const data = {
user_id: uid,
}
};
await useBaseFetch(`team/${teamId}/owner`, {
method: 'PATCH',
method: "PATCH",
body: data,
})
await refreshOrganization()
});
await refreshOrganization();
addNotification({
group: 'main',
title: 'Ownership transferred',
group: "main",
title: "Ownership transferred",
text: `The ownership of ${organization.value.name} has been successfully transferred.`,
type: 'success',
})
})
type: "success",
});
});
</script>
<style lang="scss" scoped>

View File

@@ -120,14 +120,14 @@
<p>
Changes will be applied to
<strong>{{ selectedProjects.length }}</strong> project{{
selectedProjects.length > 1 ? 's' : ''
selectedProjects.length > 1 ? "s" : ""
}}.
</p>
<ul>
<li
v-for="project in selectedProjects.slice(
0,
editLinks.showAffected ? selectedProjects.length : 3
editLinks.showAffected ? selectedProjects.length : 3,
)"
:key="project.id"
>
@@ -208,8 +208,8 @@
</div>
</div>
<div class="table">
<div class="table-row table-head">
<div class="table-cell check-cell">
<div class="table-head table-row">
<div class="check-cell table-cell">
<Checkbox
:model-value="selectedProjects === sortedProjects"
@update:model-value="
@@ -227,7 +227,7 @@
<div class="table-cell" />
</div>
<div v-for="project in sortedProjects" :key="`project-${project.id}`" class="table-row">
<div class="table-cell check-cell">
<div class="check-cell table-cell">
<Checkbox
:disabled="(project.permissions & EDIT_DETAILS) === EDIT_DETAILS"
:model-value="selectedProjects.includes(project)"
@@ -273,7 +273,7 @@
<BoxIcon />
<span>{{
$formatProjectType(
$getProjectTypeForDisplay(project.project_types[0] ?? 'project', project.loaders)
$getProjectTypeForDisplay(project.project_types[0] ?? "project", project.loaders),
)
}}</span>
</div>
@@ -298,7 +298,7 @@
</template>
<script setup>
import { Multiselect } from 'vue-multiselect'
import { Multiselect } from "vue-multiselect";
import {
BoxIcon,
SettingsIcon,
@@ -310,215 +310,215 @@ import {
SaveIcon,
SortAscendingIcon,
SortDescendingIcon,
} from '@modrinth/assets'
import { Button, Modal, Avatar, CopyCode, Badge, Checkbox } from '@modrinth/ui'
} from "@modrinth/assets";
import { Button, Modal, Avatar, CopyCode, Badge, Checkbox } from "@modrinth/ui";
import ModalCreation from '~/components/ui/ModalCreation.vue'
import OrganizationProjectTransferModal from '~/components/ui/OrganizationProjectTransferModal.vue'
import ModalCreation from "~/components/ui/ModalCreation.vue";
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
const { formatMessage } = useVIntl()
const { formatMessage } = useVIntl();
const { organization, projects, refresh } = inject('organizationContext')
const { organization, projects, refresh } = inject("organizationContext");
const auth = await useAuth()
const auth = await useAuth();
const { data: userProjects, refresh: refreshUserProjects } = await useAsyncData(
`user/${auth.value.user.id}/projects`,
() => useBaseFetch(`user/${auth.value.user.id}/projects`),
{
watch: [auth],
}
)
},
);
const usersOwnedProjects = ref([])
const usersOwnedProjects = ref([]);
watch(
() => userProjects.value,
async () => {
if (!userProjects.value) return
if (!userProjects.value.length) return
if (!userProjects.value) return;
if (!userProjects.value.length) return;
const projects = userProjects.value.filter((project) => project.organization === null)
const projects = userProjects.value.filter((project) => project.organization === null);
const teamIds = projects.map((project) => project?.team).filter((x) => x)
const teamIds = projects.map((project) => project?.team).filter((x) => x);
// Shape of teams is member[][]
const teams = await useBaseFetch(`teams?ids=${JSON.stringify(teamIds)}`, {
apiVersion: 3,
})
});
// for each team id, figure out if the user is a member, and is_owner. Then filter the projects to only include those that are owned by the user
const ownedTeamIds = teamIds.filter((_tid, i) => {
const team = teams?.[i]
if (!team) return false
const member = team.find((member) => member.user.id === auth.value.user.id)
return member && member.is_owner
})
const ownedProjects = projects.filter((project) => ownedTeamIds.includes(project.team))
usersOwnedProjects.value = ownedProjects
const team = teams?.[i];
if (!team) return false;
const member = team.find((member) => member.user.id === auth.value.user.id);
return member && member.is_owner;
});
const ownedProjects = projects.filter((project) => ownedTeamIds.includes(project.team));
usersOwnedProjects.value = ownedProjects;
}, // watch options
{ immediate: true, deep: true }
)
{ immediate: true, deep: true },
);
const onProjectTransferSubmit = async (projects) => {
try {
for (const project of projects) {
await useBaseFetch(`organization/${organization.value.id}/projects`, {
method: 'POST',
method: "POST",
body: JSON.stringify({
project_id: project.id,
}),
apiVersion: 3,
})
});
}
await refresh()
await refreshUserProjects()
await refresh();
await refreshUserProjects();
addNotification({
group: 'main',
title: 'Success',
text: 'Transferred selected projects to organization.',
type: 'success',
})
group: "main",
title: "Success",
text: "Transferred selected projects to organization.",
type: "success",
});
} catch (err) {
addNotification({
group: 'main',
title: 'An error occurred',
text: err?.data?.description || err?.message || err || 'Unknown error',
type: 'error',
})
console.error(err)
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
});
console.error(err);
}
}
};
const EDIT_DETAILS = 1 << 2
const EDIT_DETAILS = 1 << 2;
const updateSort = (inputProjects, sort, descending) => {
let sortedArray = inputProjects
let sortedArray = inputProjects;
switch (sort) {
case 'Name':
case "Name":
sortedArray = inputProjects.slice().sort((a, b) => {
return a.name.localeCompare(b.name)
})
break
case 'Status':
return a.name.localeCompare(b.name);
});
break;
case "Status":
sortedArray = inputProjects.slice().sort((a, b) => {
if (a.status < b.status) {
return -1
return -1;
}
if (a.status > b.status) {
return 1
return 1;
}
return 0
})
break
case 'Type':
return 0;
});
break;
case "Type":
sortedArray = inputProjects.slice().sort((a, b) => {
if (a.project_type < b.project_type) {
return -1
return -1;
}
if (a.project_type > b.project_type) {
return 1
return 1;
}
return 0
})
break
return 0;
});
break;
default:
break
break;
}
if (descending) {
sortedArray = sortedArray.reverse()
sortedArray = sortedArray.reverse();
}
return sortedArray
}
return sortedArray;
};
const sortedProjects = ref(updateSort(projects.value, 'Name'))
const selectedProjects = ref([])
const sortBy = ref('Name')
const descending = ref(false)
const editLinksModal = ref(null)
const sortedProjects = ref(updateSort(projects.value, "Name"));
const selectedProjects = ref([]);
const sortBy = ref("Name");
const descending = ref(false);
const editLinksModal = ref(null);
watch(
() => projects.value,
(newVal) => {
sortedProjects.value = updateSort(newVal, sortBy.value, descending.value)
}
)
sortedProjects.value = updateSort(newVal, sortBy.value, descending.value);
},
);
const emptyLinksData = {
showAffected: false,
source: {
val: '',
val: "",
clear: false,
},
discord: {
val: '',
val: "",
clear: false,
},
wiki: {
val: '',
val: "",
clear: false,
},
issues: {
val: '',
val: "",
clear: false,
},
}
};
const editLinks = ref(emptyLinksData)
const editLinks = ref(emptyLinksData);
const updateDescending = () => {
descending.value = !descending.value
sortedProjects.value = updateSort(sortedProjects.value, sortBy.value, descending.value)
}
descending.value = !descending.value;
sortedProjects.value = updateSort(sortedProjects.value, sortBy.value, descending.value);
};
const onBulkEditLinks = useClientTry(async () => {
const linkData = editLinks.value
const linkData = editLinks.value;
const baseData = {}
const baseData = {};
if (linkData.issues.clear) {
baseData.issues_url = null
baseData.issues_url = null;
} else if (linkData.issues.val.trim().length > 0) {
baseData.issues_url = linkData.issues.val.trim()
baseData.issues_url = linkData.issues.val.trim();
}
if (linkData.source.clear) {
baseData.source_url = null
baseData.source_url = null;
} else if (linkData.source.val.trim().length > 0) {
baseData.source_url = linkData.source.val.trim()
baseData.source_url = linkData.source.val.trim();
}
if (linkData.wiki.clear) {
baseData.wiki_url = null
baseData.wiki_url = null;
} else if (linkData.wiki.val.trim().length > 0) {
baseData.wiki_url = linkData.wiki.val.trim()
baseData.wiki_url = linkData.wiki.val.trim();
}
if (linkData.discord.clear) {
baseData.discord_url = null
baseData.discord_url = null;
} else if (linkData.discord.val.trim().length > 0) {
baseData.discord_url = linkData.discord.val.trim()
baseData.discord_url = linkData.discord.val.trim();
}
await useBaseFetch(`projects?ids=${JSON.stringify(selectedProjects.value.map((x) => x.id))}`, {
method: 'PATCH',
method: "PATCH",
body: JSON.stringify(baseData),
})
});
editLinksModal.value.hide()
editLinksModal.value.hide();
addNotification({
group: 'main',
title: 'Success',
group: "main",
title: "Success",
text: "Bulk edited selected project's links.",
type: 'success',
})
type: "success",
});
selectedProjects.value = []
editLinks.value = emptyLinksData
})
selectedProjects.value = [];
editLinks.value = emptyLinksData;
});
</script>
<style lang="scss" scoped>
.table {
@@ -551,7 +551,7 @@ const onBulkEditLinks = useClientTry(async () => {
.table-row {
display: grid;
grid-template: 'checkbox icon name type settings' 'checkbox icon id status settings';
grid-template: "checkbox icon name type settings" "checkbox icon id status settings";
grid-template-columns:
min-content min-content minmax(min-content, 2fr)
minmax(min-content, 1fr) min-content;
@@ -588,7 +588,7 @@ const onBulkEditLinks = useClientTry(async () => {
}
.table-head {
grid-template: 'checkbox settings';
grid-template: "checkbox settings";
grid-template-columns: min-content minmax(min-content, 1fr);
:nth-child(2),
@@ -604,7 +604,7 @@ const onBulkEditLinks = useClientTry(async () => {
@media screen and (max-width: 560px) {
.table-row {
display: grid;
grid-template: 'checkbox icon name settings' 'checkbox icon id settings' 'checkbox icon type settings' 'checkbox icon status settings';
grid-template: "checkbox icon name settings" "checkbox icon id settings" "checkbox icon type settings" "checkbox icon status settings";
grid-template-columns: min-content min-content minmax(min-content, 1fr) min-content;
:nth-child(5) {
@@ -613,7 +613,7 @@ const onBulkEditLinks = useClientTry(async () => {
}
.table-head {
grid-template: 'checkbox settings';
grid-template: "checkbox settings";
grid-template-columns: min-content minmax(min-content, 1fr);
}
}
@@ -652,7 +652,7 @@ const onBulkEditLinks = useClientTry(async () => {
width: -moz-fit-content;
}
.label-button[data-active='true'] {
.label-button[data-active="true"] {
--background-color: var(--color-red);
--text-color: var(--color-brand-inverted);
}