mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
refactor: instance groups to be identified by ID instead of name, names become display data
This commit is contained in:
@@ -19,8 +19,10 @@ import { useRouter } from 'vue-router'
|
||||
|
||||
import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import { install_duplicate_instance } from '@/helpers/install'
|
||||
import { edit, edit_icon, list, remove } from '@/helpers/instance'
|
||||
import { edit, edit_icon, remove } from '@/helpers/instance'
|
||||
import { create_group, list_groups } from '@/helpers/instance-groups'
|
||||
import { injectInstanceSettings } from '@/providers/instance-settings'
|
||||
|
||||
import type { GameInstance } from '../../../helpers/types'
|
||||
@@ -38,7 +40,7 @@ const releaseChannelOptions: ReleaseChannel[] = ['release', 'beta', 'alpha']
|
||||
|
||||
const title = ref(instance.value.name)
|
||||
const icon: Ref<string | undefined> = ref(instance.value.icon_path)
|
||||
const groups = ref([...instance.value.groups])
|
||||
const groupIds = ref([...instance.value.group_ids])
|
||||
const savingReleaseChannel = ref(false)
|
||||
const selectedReleaseChannel = ref<ReleaseChannel>(instance.value.update_channel)
|
||||
const releaseChannelDisabledItems = computed<ReleaseChannel[]>(() =>
|
||||
@@ -57,10 +59,7 @@ async function duplicateInstance() {
|
||||
})
|
||||
}
|
||||
|
||||
const allInstances = ref((await list()) as GameInstance[])
|
||||
const availableGroups = computed(() => [
|
||||
...new Set([...allInstances.value.flatMap((instance) => instance.groups), ...groups.value]),
|
||||
])
|
||||
const availableGroups = ref(await list_groups())
|
||||
|
||||
function formatReleaseChannelLabel(channel: ReleaseChannel) {
|
||||
switch (channel) {
|
||||
@@ -135,28 +134,34 @@ async function setIcon() {
|
||||
|
||||
const editInstanceObject = computed(() => ({
|
||||
name: title.value.trim().substring(0, 32) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
group_ids: groupIds.value,
|
||||
}))
|
||||
|
||||
const toggleGroup = (group: string) => {
|
||||
if (groups.value.includes(group)) {
|
||||
groups.value = groups.value.filter((x) => x !== group)
|
||||
const toggleGroup = (groupId: string) => {
|
||||
if (groupIds.value.includes(groupId)) {
|
||||
groupIds.value = groupIds.value.filter((id) => id !== groupId)
|
||||
} else {
|
||||
groups.value.push(group)
|
||||
groupIds.value.push(groupId)
|
||||
}
|
||||
}
|
||||
|
||||
const addCategory = () => {
|
||||
const addCategory = async () => {
|
||||
const text = newCategoryInput.value.trim()
|
||||
|
||||
if (text.length > 0) {
|
||||
groups.value.push(text.substring(0, 32))
|
||||
newCategoryInput.value = ''
|
||||
try {
|
||||
const group = await create_group(text.substring(0, 32))
|
||||
availableGroups.value.push(group)
|
||||
groupIds.value.push(group.id)
|
||||
newCategoryInput.value = ''
|
||||
} catch (error) {
|
||||
handleError(toError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
[title, groups, groups],
|
||||
[title, groupIds],
|
||||
async () => {
|
||||
if (removing.value) return
|
||||
await edit(instance.value.id, editInstanceObject.value).catch(handleError)
|
||||
@@ -369,10 +374,10 @@ const messages = defineMessages({
|
||||
<div class="flex flex-col gap-1">
|
||||
<Checkbox
|
||||
v-for="group in availableGroups"
|
||||
:key="group"
|
||||
:model-value="groups.includes(group)"
|
||||
:label="group"
|
||||
@click="toggleGroup(group)"
|
||||
:key="group.id"
|
||||
:model-value="groupIds.includes(group.id)"
|
||||
:label="group.name"
|
||||
@click="toggleGroup(group.id)"
|
||||
/>
|
||||
<div class="flex gap-2 items-center">
|
||||
<StyledInput
|
||||
|
||||
@@ -80,7 +80,8 @@ const removingFromGroup = ref(false)
|
||||
const selectedInstanceCount = computed(() => selectedLibraryInstanceIds.value.size)
|
||||
const selectedGroupedInstances = computed(() =>
|
||||
instances.value.filter(
|
||||
(instance) => selectedLibraryInstanceIds.value.has(instance.id) && instance.groups.length > 0,
|
||||
(instance) =>
|
||||
selectedLibraryInstanceIds.value.has(instance.id) && instance.group_ids.length > 0,
|
||||
),
|
||||
)
|
||||
const busy = computed(() => deleting.value || removingFromGroup.value)
|
||||
@@ -109,7 +110,7 @@ async function removeSelectedInstancesFromGroups() {
|
||||
|
||||
removingFromGroup.value = true
|
||||
const results = await Promise.allSettled(
|
||||
selectedGroupedInstances.value.map((instance) => edit(instance.id, { groups: [] })),
|
||||
selectedGroupedInstances.value.map((instance) => edit(instance.id, { group_ids: [] })),
|
||||
)
|
||||
|
||||
for (const result of results) {
|
||||
|
||||
@@ -40,7 +40,6 @@ const {
|
||||
isSectionCollapsed,
|
||||
setSectionCollapsed,
|
||||
deleteGroup,
|
||||
isValidGroupName,
|
||||
renameGroup,
|
||||
handleInstanceContextMenu,
|
||||
displayState,
|
||||
@@ -57,7 +56,7 @@ const groupNameInput = ref<InstanceType<typeof InlineEditableText>>()
|
||||
const confirmDeleteGroupModal = ref<InstanceType<typeof NewModal>>()
|
||||
const deletingGroup = ref(false)
|
||||
const groupName = ref(props.instanceGroup.key)
|
||||
const isUngrouped = computed(() => props.instanceGroup.key === 'None')
|
||||
const isUngrouped = computed(() => props.instanceGroup.id === 'group:none')
|
||||
const groupContextMenuOpen = ref(false)
|
||||
const isGroupToggleBlocked = computed(
|
||||
() => groupContextMenuOpen.value || Boolean(groupNameInput.value?.isEditing),
|
||||
@@ -74,7 +73,7 @@ useDroppable({
|
||||
element: groupDropTarget,
|
||||
disabled: computed(() => displayState.value.group !== 'Group'),
|
||||
data: computed(() => ({
|
||||
groupName: props.instanceGroup.key,
|
||||
groupId: props.instanceGroup.id,
|
||||
})),
|
||||
})
|
||||
|
||||
@@ -107,21 +106,17 @@ const messages = defineMessages({
|
||||
id: 'app.library.group.name-reserved',
|
||||
defaultMessage: '"None" is reserved and cannot be used as a group name.',
|
||||
},
|
||||
groupNameDuplicate: {
|
||||
id: 'app.library.group.name-duplicate',
|
||||
defaultMessage: 'A group with this name already exists.',
|
||||
},
|
||||
deleteGroupDescription: {
|
||||
id: 'app.library.group.delete-description',
|
||||
defaultMessage: 'Instances in this group will be ungrouped.',
|
||||
},
|
||||
})
|
||||
|
||||
function openInstanceContextMenu(event: MouseEvent, instanceId: string, instanceGroupName: string) {
|
||||
function openInstanceContextMenu(event: MouseEvent, instanceId: string, instanceGroupId: string) {
|
||||
const instanceComponent = instanceComponents.get(instanceId)
|
||||
if (!instanceComponent) return
|
||||
|
||||
handleInstanceContextMenu(event, instanceComponent, instanceGroupName)
|
||||
handleInstanceContextMenu(event, instanceComponent, instanceGroupId)
|
||||
}
|
||||
|
||||
function setInstanceComponent(instanceId: string, component: unknown) {
|
||||
@@ -136,7 +131,7 @@ async function removeGroup() {
|
||||
if (deletingGroup.value) return
|
||||
|
||||
deletingGroup.value = true
|
||||
const deleted = await deleteGroup(props.instanceGroup.key)
|
||||
const deleted = await deleteGroup(props.instanceGroup.id)
|
||||
deletingGroup.value = false
|
||||
|
||||
if (deleted) {
|
||||
@@ -217,8 +212,6 @@ function validateGroupName(value: string) {
|
||||
reason = formatMessage(messages.groupNameTooLong)
|
||||
} else if (normalizedGroupName.toLowerCase() === 'none') {
|
||||
reason = formatMessage(messages.groupNameReserved)
|
||||
} else if (!isValidGroupName(normalizedGroupName, props.instanceGroup.key)) {
|
||||
reason = formatMessage(messages.groupNameDuplicate)
|
||||
}
|
||||
|
||||
if (reason) {
|
||||
@@ -234,7 +227,7 @@ function validateGroupName(value: string) {
|
||||
}
|
||||
|
||||
async function updateGroupName(value: string) {
|
||||
return await renameGroup(props.instanceGroup.id, props.instanceGroup.key, value)
|
||||
return await renameGroup(props.instanceGroup.id, value)
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -272,8 +265,8 @@ watch(
|
||||
<div
|
||||
v-if="
|
||||
activeInstanceGroupDrag &&
|
||||
instanceGroupDragTarget === instanceGroup.key &&
|
||||
getInstanceGroupDropState(instanceGroup.key).canDrop
|
||||
instanceGroupDragTarget === instanceGroup.id &&
|
||||
getInstanceGroupDropState(instanceGroup.id).canDrop
|
||||
"
|
||||
class="pointer-events-none absolute -inset-2 inset-y-0 z-20 rounded-xl border-2 opacity-50 border-dashed border-brand bg-transparent"
|
||||
/>
|
||||
@@ -344,10 +337,10 @@ watch(
|
||||
</div>
|
||||
<Accordion
|
||||
ref="groupAccordion"
|
||||
:open-by-default="hideHeader || !isSectionCollapsed(instanceGroup.key)"
|
||||
:open-by-default="hideHeader || !isSectionCollapsed(instanceGroup.id)"
|
||||
class="w-full"
|
||||
@on-open="setSectionCollapsed(instanceGroup.key, false)"
|
||||
@on-close="setSectionCollapsed(instanceGroup.key, true)"
|
||||
@on-open="setSectionCollapsed(instanceGroup.id, false)"
|
||||
@on-close="setSectionCollapsed(instanceGroup.id, true)"
|
||||
>
|
||||
<section
|
||||
class="grid min-h-[45px] mt-2.5 w-full grid-cols-[repeat(auto-fill,minmax(20rem,22rem))] gap-3 overflow-y-auto scroll-smooth"
|
||||
@@ -356,13 +349,13 @@ watch(
|
||||
<Instance
|
||||
:ref="(component: unknown) => setInstanceComponent(instance.id, component)"
|
||||
:instance="instance"
|
||||
:instance-group-name="instanceGroup.key"
|
||||
:instance-group-id="instanceGroup.id"
|
||||
:is-selection-anchor="selectionAnchorInstanceId === instance.id"
|
||||
@toggle-selection="
|
||||
(shiftKey: boolean) => emit('toggle-selection', instance.id, shiftKey)
|
||||
"
|
||||
@contextmenu.prevent.stop="
|
||||
(event: MouseEvent) => openInstanceContextMenu(event, instance.id, instanceGroup.key)
|
||||
(event: MouseEvent) => openInstanceContextMenu(event, instance.id, instanceGroup.id)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ type InstanceDragData = {
|
||||
}
|
||||
|
||||
type InstanceGroupDndDropData = {
|
||||
groupName: string
|
||||
groupId: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -134,15 +134,15 @@ function handleDragMove(event: DragMoveEvent) {
|
||||
|
||||
function handleDragOver(event: DragOverEvent) {
|
||||
const targetData = event.operation.target?.data as InstanceGroupDndDropData | undefined
|
||||
setInstanceGroupDragTarget(targetData?.groupName ?? null)
|
||||
setInstanceGroupDragTarget(targetData?.groupId ?? null)
|
||||
}
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const targetData = event.operation.target?.data as InstanceGroupDndDropData | undefined
|
||||
if (!event.canceled && targetData) {
|
||||
const dropState = getInstanceGroupDropState(targetData.groupName)
|
||||
const dropState = getInstanceGroupDropState(targetData.groupId)
|
||||
if (dropState.canDrop) {
|
||||
void moveDraggedInstancesToGroup(targetData.groupName, dropState.operation === 'add')
|
||||
void moveDraggedInstancesToGroup(targetData.groupId, dropState.operation === 'add')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ const {
|
||||
|
||||
const props = defineProps<{
|
||||
instance: GameInstance
|
||||
instanceGroupName: string
|
||||
instanceGroupId: string
|
||||
isSelectionAnchor?: boolean
|
||||
}>()
|
||||
|
||||
@@ -60,12 +60,12 @@ const keys = useMagicKeys()
|
||||
const holdingShift = computed(() => keys.shift.value)
|
||||
const isPartOfActiveDrag = computed(() => activeDraggedInstanceIds.value.has(props.instance.id))
|
||||
const { isDragging } = useDraggable({
|
||||
id: computed(() => `instance:${props.instanceGroupName}:${props.instance.id}`),
|
||||
id: computed(() => `instance:${props.instanceGroupId}:${props.instance.id}`),
|
||||
element: instanceCard,
|
||||
disabled: computed(() => displayState.value.group !== 'Group'),
|
||||
data: computed(() => ({
|
||||
instanceId: props.instance.id,
|
||||
fromGroup: props.instanceGroupName,
|
||||
fromGroup: props.instanceGroupId,
|
||||
})),
|
||||
})
|
||||
|
||||
@@ -218,7 +218,7 @@ onUnmounted(() => unlisten())
|
||||
}"
|
||||
data-library-instance-card
|
||||
:data-instance-id="instance.id"
|
||||
:data-instance-group="instanceGroupName"
|
||||
:data-instance-group="instanceGroupId"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:aria-label="
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
:maxlength="32"
|
||||
@click="groupNameInput?.select()"
|
||||
/>
|
||||
<span v-if="newGroupNameExists" class="text-sm font-medium text-red">
|
||||
A group with this name already exists.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-px bg-divider" />
|
||||
@@ -62,8 +59,8 @@
|
||||
/>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="truncate font-semibold text-contrast">{{ instance.name }}</span>
|
||||
<TagItem v-if="instance.groups[0]" class="shrink-0">
|
||||
{{ instance.groups[0] }}
|
||||
<TagItem v-if="instance.group_ids[0]" class="shrink-0">
|
||||
{{ groupNamesById.get(instance.group_ids[0]) ?? 'Unknown group' }}
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,10 +71,10 @@
|
||||
<CheckIcon v-if="selectedNewGroupInstanceIds.has(instance.id)" />
|
||||
{{
|
||||
selectedNewGroupInstanceIds.has(instance.id)
|
||||
? instance.groups.length > 0
|
||||
? instance.group_ids.length > 0
|
||||
? 'Moved'
|
||||
: 'Added'
|
||||
: instance.groups.length > 0
|
||||
: instance.group_ids.length > 0
|
||||
? 'Move'
|
||||
: 'Add'
|
||||
}}
|
||||
@@ -111,17 +108,17 @@
|
||||
import { CheckIcon, PlusIcon, SearchIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, NewModal, StyledInput, TagItem } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { useLibrary } from '@/components/ui/library/use-library'
|
||||
|
||||
const {
|
||||
isNewGroupModalOpen,
|
||||
libraryGroups,
|
||||
newGroupName,
|
||||
newGroupSearch,
|
||||
selectedNewGroupInstanceIds,
|
||||
creatingGroup,
|
||||
newGroupNameExists,
|
||||
newGroupInstances,
|
||||
canCreateGroup,
|
||||
closeNewGroupModal,
|
||||
@@ -131,6 +128,9 @@ const {
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const groupNameInput = ref<InstanceType<typeof StyledInput>>()
|
||||
const groupNamesById = computed(
|
||||
() => new Map(libraryGroups.value.map((group) => [group.id, group.name])),
|
||||
)
|
||||
|
||||
watch(isNewGroupModalOpen, (open) => {
|
||||
if (open) {
|
||||
|
||||
@@ -84,7 +84,6 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
})
|
||||
const serverProjectIds = ref(new Set<string>())
|
||||
const libraryGroups = ref<InstanceGroupDefinition[]>([])
|
||||
const groupIdsByName = ref(new Map<string, string>())
|
||||
const isNewGroupModalOpen = ref(false)
|
||||
const newGroupName = ref('')
|
||||
const newGroupSearch = ref('')
|
||||
@@ -102,7 +101,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const isAddingInstanceToGroup = ref(false)
|
||||
const instanceOptions = ref<InstanceContextMenu | null>(null)
|
||||
const currentDeleteInstanceId = ref<string | null>(null)
|
||||
const currentContextGroupName = ref<string | null>(null)
|
||||
const currentContextGroupId = ref<string | null>(null)
|
||||
const confirmDeleteModal = ref<ConfirmDeleteModal | null>(null)
|
||||
|
||||
const displayState = useStorage<{
|
||||
@@ -129,10 +128,8 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const groupNames = computed(
|
||||
() =>
|
||||
new Set(
|
||||
[
|
||||
...libraryGroups.value.map((group) => group.name),
|
||||
...instances.value.flatMap((instance) => instance.groups),
|
||||
]
|
||||
libraryGroups.value
|
||||
.map((group) => group.name)
|
||||
.map((group) => group.trim())
|
||||
.filter((group) => group && group.toLowerCase() !== 'none'),
|
||||
),
|
||||
@@ -141,9 +138,6 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
() => new Set(['none', ...Array.from(groupNames.value, (group) => group.toLowerCase())]),
|
||||
)
|
||||
const normalizedNewGroupName = computed(() => newGroupName.value.trim().substring(0, 32))
|
||||
const newGroupNameExists = computed(() =>
|
||||
existingGroupNames.value.has(normalizedNewGroupName.value.toLowerCase()),
|
||||
)
|
||||
const newGroupInstances = computed(() => {
|
||||
const query = newGroupSearch.value.trim().toLowerCase()
|
||||
|
||||
@@ -151,25 +145,19 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
.filter((instance) => !query || instance.name.toLowerCase().includes(query))
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
const groupedDifference = Number(a.groups.length > 0) - Number(b.groups.length > 0)
|
||||
const groupedDifference = Number(a.group_ids.length > 0) - Number(b.group_ids.length > 0)
|
||||
if (groupedDifference !== 0) return groupedDifference
|
||||
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
})
|
||||
const canCreateGroup = computed(
|
||||
() =>
|
||||
normalizedNewGroupName.value.length > 0 && !newGroupNameExists.value && !creatingGroup.value,
|
||||
() => normalizedNewGroupName.value.length > 0 && !creatingGroup.value,
|
||||
)
|
||||
|
||||
const refreshGroups = async () => {
|
||||
try {
|
||||
const groups = await listInstanceGroups()
|
||||
libraryGroups.value = groups
|
||||
groupIdsByName.value = new Map([
|
||||
...groupIdsByName.value,
|
||||
...groups.map((group) => [group.name, group.id] as const),
|
||||
])
|
||||
libraryGroups.value = await listInstanceGroups()
|
||||
} catch (error) {
|
||||
handleError(toError(error))
|
||||
}
|
||||
@@ -259,66 +247,61 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
break
|
||||
}
|
||||
|
||||
const groupedInstances = new Map<string, GameInstance[]>()
|
||||
const addToGroup = (key: string, instance: GameInstance) => {
|
||||
const group = groupedInstances.get(key) ?? []
|
||||
group.push(instance)
|
||||
groupedInstances.set(key, group)
|
||||
const groupedInstances = new Map<string, { name: string; instances: GameInstance[] }>()
|
||||
const addToGroup = (id: string, name: string, instance: GameInstance) => {
|
||||
const group = groupedInstances.get(id) ?? { name, instances: [] }
|
||||
group.instances.push(instance)
|
||||
groupedInstances.set(id, group)
|
||||
}
|
||||
const groupsById = new Map(libraryGroups.value.map((group) => [group.id, group]))
|
||||
|
||||
for (const instance of visibleInstances) {
|
||||
switch (displayState.value.group) {
|
||||
case 'Loader':
|
||||
addToGroup(formatLoader(formatMessage, instance.loader), instance)
|
||||
{
|
||||
const name = formatLoader(formatMessage, instance.loader)
|
||||
addToGroup(`Loader:${name}`, name, instance)
|
||||
}
|
||||
break
|
||||
case 'Game version':
|
||||
addToGroup(instance.game_version, instance)
|
||||
addToGroup(`Game version:${instance.game_version}`, instance.game_version, instance)
|
||||
break
|
||||
case 'Group':
|
||||
for (const group of instance.groups.length > 0 ? instance.groups : ['None']) {
|
||||
addToGroup(group, instance)
|
||||
if (instance.group_ids.length === 0) {
|
||||
addToGroup('group:none', 'None', instance)
|
||||
} else {
|
||||
for (const groupId of instance.group_ids) {
|
||||
const group = groupsById.get(groupId)
|
||||
addToGroup(groupId, group?.name ?? groupId, instance)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'None':
|
||||
addToGroup('None', instance)
|
||||
addToGroup('None:None', 'None', instance)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const resolveGroupId = (groupName: string) =>
|
||||
groupIdsByName.value.get(groupName) ?? `group-name:${groupName}`
|
||||
|
||||
if (displayState.value.group === 'Group') {
|
||||
if (!groupedInstances.has('None')) {
|
||||
groupedInstances.set('None', [])
|
||||
if (!groupedInstances.has('group:none')) {
|
||||
groupedInstances.set('group:none', { name: 'None', instances: [] })
|
||||
}
|
||||
|
||||
const populatedGroupIds = new Set(
|
||||
instances.value.flatMap((instance) =>
|
||||
instance.groups.map((groupName) => resolveGroupId(groupName)),
|
||||
),
|
||||
)
|
||||
|
||||
for (const group of libraryGroups.value) {
|
||||
if (!populatedGroupIds.has(group.id) && !groupedInstances.has(group.name)) {
|
||||
groupedInstances.set(group.name, [])
|
||||
if (!groupedInstances.has(group.id)) {
|
||||
groupedInstances.set(group.id, { name: group.name, instances: [] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groups = Array.from(groupedInstances, ([key, groupInstances]) => ({
|
||||
id:
|
||||
displayState.value.group === 'Group'
|
||||
? key === 'None'
|
||||
? 'group:none'
|
||||
: resolveGroupId(key)
|
||||
: `${displayState.value.group}:${key}`,
|
||||
key,
|
||||
instances: groupInstances,
|
||||
const groups = Array.from(groupedInstances, ([id, group]) => ({
|
||||
id,
|
||||
key: group.name,
|
||||
instances: group.instances,
|
||||
}))
|
||||
|
||||
if (displayState.value.sortBy === 'Name') {
|
||||
groups.sort((a, b) => a.key.localeCompare(b.key))
|
||||
groups.sort((a, b) => a.key.localeCompare(b.key) || a.id.localeCompare(b.id))
|
||||
}
|
||||
|
||||
if (displayState.value.group === 'Game version') {
|
||||
@@ -329,20 +312,20 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
groups.sort((a, b) => {
|
||||
if (a.key === 'None') return 1
|
||||
if (b.key === 'None') return -1
|
||||
return a.key.localeCompare(b.key)
|
||||
return a.key.localeCompare(b.key) || a.id.localeCompare(b.id)
|
||||
})
|
||||
}
|
||||
|
||||
return groups
|
||||
})
|
||||
|
||||
const getSectionKey = (sectionName: string) => `${displayState.value.group}:${sectionName}`
|
||||
const getSectionKey = (sectionId: string) => `${displayState.value.group}:${sectionId}`
|
||||
|
||||
const isSectionCollapsed = (sectionName: string) =>
|
||||
collapsedSectionKeys.value.has(getSectionKey(sectionName))
|
||||
const isSectionCollapsed = (sectionId: string) =>
|
||||
collapsedSectionKeys.value.has(getSectionKey(sectionId))
|
||||
|
||||
const setSectionCollapsed = (sectionName: string, collapsed: boolean) => {
|
||||
const sectionKey = getSectionKey(sectionName)
|
||||
const setSectionCollapsed = (sectionId: string, collapsed: boolean) => {
|
||||
const sectionKey = getSectionKey(sectionId)
|
||||
const collapsedSections = new Set(displayState.value.collapsedGroups)
|
||||
|
||||
if (collapsed) {
|
||||
@@ -354,8 +337,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
displayState.value.collapsedGroups = [...collapsedSections]
|
||||
}
|
||||
|
||||
const normalizeInstanceGroupName = (groupName: string) =>
|
||||
groupName === 'None' ? null : groupName
|
||||
const normalizeInstanceGroupId = (groupId: string) => (groupId === 'group:none' ? null : groupId)
|
||||
|
||||
const updateInstanceGroupDrag = (
|
||||
pointer?: { x: number; y: number },
|
||||
@@ -372,7 +354,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
|
||||
const startInstanceGroupDrag = (
|
||||
instanceId: string,
|
||||
groupName: string,
|
||||
groupId: string,
|
||||
pointer?: { x: number; y: number },
|
||||
altKey = false,
|
||||
) => {
|
||||
@@ -390,7 +372,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
activeInstanceGroupDrag.value = {
|
||||
instanceIds,
|
||||
primaryInstanceId: instanceId,
|
||||
fromGroup: normalizeInstanceGroupName(groupName),
|
||||
fromGroup: normalizeInstanceGroupId(groupId),
|
||||
}
|
||||
updateInstanceGroupDrag(pointer, altKey)
|
||||
}
|
||||
@@ -401,26 +383,26 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
isAddingInstanceToGroup.value = false
|
||||
}
|
||||
|
||||
const setInstanceGroupDragTarget = (groupName: string | null) => {
|
||||
instanceGroupDragTarget.value = groupName
|
||||
const setInstanceGroupDragTarget = (groupId: string | null) => {
|
||||
instanceGroupDragTarget.value = groupId
|
||||
}
|
||||
|
||||
const getInstanceGroupDropState = (groupName: string) => {
|
||||
const getInstanceGroupDropState = (groupId: string) => {
|
||||
const drag = activeInstanceGroupDrag.value
|
||||
const draggedInstances = drag
|
||||
? instances.value.filter((instance) => drag.instanceIds.includes(instance.id))
|
||||
: []
|
||||
const toGroup = normalizeInstanceGroupName(groupName)
|
||||
const toGroup = normalizeInstanceGroupId(groupId)
|
||||
const operation = isAddingInstanceToGroup.value ? 'add' : 'move'
|
||||
const canAddToGroup = operation !== 'add' || toGroup !== null
|
||||
const hasChanges = draggedInstances.some((instance) => {
|
||||
if (operation === 'add') {
|
||||
return toGroup !== null && !instance.groups.includes(toGroup)
|
||||
return toGroup !== null && !instance.group_ids.includes(toGroup)
|
||||
}
|
||||
|
||||
return toGroup === null
|
||||
? instance.groups.length > 0
|
||||
: instance.groups.length !== 1 || instance.groups[0] !== toGroup
|
||||
? instance.group_ids.length > 0
|
||||
: instance.group_ids.length !== 1 || instance.group_ids[0] !== toGroup
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -444,12 +426,12 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
return
|
||||
})
|
||||
|
||||
const moveDraggedInstancesToGroup = async (groupName: string, addToGroup = false) => {
|
||||
const moveDraggedInstancesToGroup = async (groupId: string, addToGroup = false) => {
|
||||
const drag = activeInstanceGroupDrag.value
|
||||
const toGroup = normalizeInstanceGroupName(groupName)
|
||||
const toGroup = normalizeInstanceGroupId(groupId)
|
||||
if (!drag) return false
|
||||
|
||||
const dropState = getInstanceGroupDropState(groupName)
|
||||
const dropState = getInstanceGroupDropState(groupId)
|
||||
if (!dropState.canDrop) return false
|
||||
|
||||
const shouldAdd = addToGroup && toGroup !== null
|
||||
@@ -458,12 +440,12 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
)
|
||||
const results = await Promise.allSettled(
|
||||
draggedInstances.map((instance) => {
|
||||
const nextGroups = shouldAdd ? [...instance.groups] : toGroup ? [toGroup] : []
|
||||
if (shouldAdd && toGroup && !nextGroups.includes(toGroup)) {
|
||||
nextGroups.push(toGroup)
|
||||
const nextGroupIds = shouldAdd ? [...instance.group_ids] : toGroup ? [toGroup] : []
|
||||
if (shouldAdd && toGroup && !nextGroupIds.includes(toGroup)) {
|
||||
nextGroupIds.push(toGroup)
|
||||
}
|
||||
|
||||
return edit(instance.id, { groups: nextGroups })
|
||||
return edit(instance.id, { group_ids: nextGroupIds })
|
||||
}),
|
||||
)
|
||||
let movedInstanceCount = 0
|
||||
@@ -562,14 +544,13 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
...libraryGroups.value.filter((existingGroup) => existingGroup.id !== group.id),
|
||||
group,
|
||||
]
|
||||
groupIdsByName.value = new Map(groupIdsByName.value).set(group.name, group.id)
|
||||
|
||||
await Promise.all(
|
||||
instances.value
|
||||
.filter((instance) => selectedNewGroupInstanceIds.value.has(instance.id))
|
||||
.map((instance) =>
|
||||
edit(instance.id, {
|
||||
groups: [group.name],
|
||||
group_ids: [group.id],
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -583,13 +564,10 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteGroup = async (groupName: string) => {
|
||||
const deleteGroup = async (groupId: string) => {
|
||||
try {
|
||||
await deleteInstanceGroup(groupName)
|
||||
libraryGroups.value = libraryGroups.value.filter((group) => group.name !== groupName)
|
||||
const nextGroupIdsByName = new Map(groupIdsByName.value)
|
||||
nextGroupIdsByName.delete(groupName)
|
||||
groupIdsByName.value = nextGroupIdsByName
|
||||
await deleteInstanceGroup(groupId)
|
||||
libraryGroups.value = libraryGroups.value.filter((group) => group.id !== groupId)
|
||||
return true
|
||||
} catch (error) {
|
||||
handleError(toError(error))
|
||||
@@ -598,60 +576,19 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
}
|
||||
}
|
||||
|
||||
const isValidGroupName = (groupName: string, currentGroupName: string) => {
|
||||
const normalizedGroupName = groupName.trim()
|
||||
if (
|
||||
normalizedGroupName.length === 0 ||
|
||||
normalizedGroupName.length > 32 ||
|
||||
normalizedGroupName.toLowerCase() === 'none'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !Array.from(groupNames.value).some(
|
||||
(existingGroupName) =>
|
||||
existingGroupName.toLowerCase() === normalizedGroupName.toLowerCase() &&
|
||||
existingGroupName !== currentGroupName,
|
||||
)
|
||||
}
|
||||
|
||||
const renameGroup = async (groupId: string, oldName: string, newName: string) => {
|
||||
const renameGroup = async (groupId: string, newName: string) => {
|
||||
const normalizedNewName = newName.trim()
|
||||
if (oldName === normalizedNewName) return true
|
||||
if (!isValidGroupName(normalizedNewName, oldName)) return false
|
||||
|
||||
const previousNewNameId = groupIdsByName.value.get(normalizedNewName)
|
||||
groupIdsByName.value = new Map(groupIdsByName.value).set(normalizedNewName, groupId)
|
||||
const currentGroup = libraryGroups.value.find((group) => group.id === groupId)
|
||||
if (currentGroup?.name === normalizedNewName) return true
|
||||
|
||||
try {
|
||||
const renamedGroup = await renameInstanceGroup(oldName, normalizedNewName)
|
||||
const renamedGroup = await renameInstanceGroup(groupId, normalizedNewName)
|
||||
libraryGroups.value = [
|
||||
...libraryGroups.value.filter((group) => group.id !== groupId),
|
||||
renamedGroup,
|
||||
]
|
||||
groupIdsByName.value = new Map(groupIdsByName.value).set(renamedGroup.name, renamedGroup.id)
|
||||
|
||||
const oldSectionKey = getSectionKey(oldName)
|
||||
const newSectionKey = getSectionKey(renamedGroup.name)
|
||||
if (collapsedSectionKeys.value.has(oldSectionKey)) {
|
||||
displayState.value.collapsedGroups = displayState.value.collapsedGroups.map((sectionKey) =>
|
||||
sectionKey === oldSectionKey ? newSectionKey : sectionKey,
|
||||
)
|
||||
}
|
||||
|
||||
if (currentContextGroupName.value === oldName) {
|
||||
currentContextGroupName.value = renamedGroup.name
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
const nextGroupIdsByName = new Map(groupIdsByName.value)
|
||||
if (previousNewNameId) {
|
||||
nextGroupIdsByName.set(normalizedNewName, previousNewNameId)
|
||||
} else {
|
||||
nextGroupIdsByName.delete(normalizedNewName)
|
||||
}
|
||||
groupIdsByName.value = nextGroupIdsByName
|
||||
handleError(toError(error))
|
||||
await refreshGroups()
|
||||
return false
|
||||
@@ -672,11 +609,11 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
const handleInstanceContextMenu = (
|
||||
event: MouseEvent,
|
||||
item: InstanceCard,
|
||||
instanceGroupName: string,
|
||||
instanceGroupId: string,
|
||||
) => {
|
||||
currentContextGroupName.value =
|
||||
displayState.value.group === 'Group' && instanceGroupName !== 'None'
|
||||
? instanceGroupName
|
||||
currentContextGroupId.value =
|
||||
displayState.value.group === 'Group' && instanceGroupId !== 'group:none'
|
||||
? instanceGroupId
|
||||
: null
|
||||
|
||||
const baseOptions = [
|
||||
@@ -685,7 +622,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
{ name: 'duplicate' },
|
||||
{ name: 'open' },
|
||||
{ name: 'copy' },
|
||||
...(currentContextGroupName.value
|
||||
...(currentContextGroupId.value
|
||||
? [{ name: 'remove_from_group' }, { type: 'divider' }]
|
||||
: [{ type: 'divider' }]),
|
||||
{ name: 'delete', color: 'danger' },
|
||||
@@ -729,10 +666,12 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
await navigator.clipboard.writeText(item.instance.id)
|
||||
break
|
||||
case 'remove_from_group':
|
||||
if (currentContextGroupName.value) {
|
||||
const groupName = currentContextGroupName.value
|
||||
if (currentContextGroupId.value) {
|
||||
const groupId = currentContextGroupId.value
|
||||
await edit(item.instance.id, {
|
||||
groups: item.instance.groups.filter((group) => group !== groupName),
|
||||
group_ids: item.instance.group_ids.filter(
|
||||
(instanceGroupId) => instanceGroupId !== groupId,
|
||||
),
|
||||
}).catch((error) => handleError(toError(error)))
|
||||
}
|
||||
break
|
||||
@@ -745,6 +684,7 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
|
||||
return {
|
||||
instances,
|
||||
libraryGroups,
|
||||
search,
|
||||
filters,
|
||||
displayState,
|
||||
@@ -762,7 +702,6 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
instanceGroupDragStatus,
|
||||
isAddingInstanceToGroup,
|
||||
creatingGroup,
|
||||
newGroupNameExists,
|
||||
newGroupInstances,
|
||||
canCreateGroup,
|
||||
instanceOptions,
|
||||
@@ -783,7 +722,6 @@ function createLibraryState(instances: Ref<GameInstance[]>) {
|
||||
toggleLibraryInstanceSelection,
|
||||
createGroup,
|
||||
deleteGroup,
|
||||
isValidGroupName,
|
||||
renameGroup,
|
||||
deleteInstance,
|
||||
handleInstanceContextMenu,
|
||||
|
||||
@@ -13,13 +13,10 @@ export async function create_group(name: string): Promise<InstanceGroupDefinitio
|
||||
return await invoke('plugin:instance|instance_create_group', { name })
|
||||
}
|
||||
|
||||
export async function rename_group(
|
||||
oldName: string,
|
||||
newName: string,
|
||||
): Promise<InstanceGroupDefinition> {
|
||||
return await invoke('plugin:instance|instance_rename_group', { oldName, newName })
|
||||
export async function rename_group(id: string, newName: string): Promise<InstanceGroupDefinition> {
|
||||
return await invoke('plugin:instance|instance_rename_group', { id, newName })
|
||||
}
|
||||
|
||||
export async function delete_group(name: string): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_delete_group', { name })
|
||||
export async function delete_group(id: string): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_delete_group', { id })
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export type GameInstance = {
|
||||
loader: InstanceLoader
|
||||
loader_version?: string
|
||||
|
||||
groups: string[]
|
||||
group_ids: string[]
|
||||
|
||||
link?: InstanceLink | null
|
||||
shared_instance?: SharedInstanceAttachment | null
|
||||
|
||||
@@ -83,7 +83,7 @@ pub struct Instance {
|
||||
pub protocol_version: Option<u32>,
|
||||
pub loader: ModLoader,
|
||||
pub loader_version: Option<String>,
|
||||
pub groups: Vec<String>,
|
||||
pub group_ids: Vec<String>,
|
||||
pub link: Option<InstanceLink>,
|
||||
pub shared_instance: Option<SharedInstanceAttachment>,
|
||||
pub quarantined: bool,
|
||||
@@ -179,7 +179,7 @@ pub struct EditInstance {
|
||||
)]
|
||||
pub loader_version: Option<Option<String>>,
|
||||
|
||||
pub groups: Option<Vec<String>>,
|
||||
pub group_ids: Option<Vec<String>>,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
@@ -246,7 +246,7 @@ impl From<InstanceMetadata> for Instance {
|
||||
protocol_version: metadata.applied_content_set.protocol_version,
|
||||
loader: metadata.applied_content_set.loader,
|
||||
loader_version: metadata.applied_content_set.loader_version,
|
||||
groups: metadata.groups,
|
||||
group_ids: metadata.group_ids,
|
||||
link: InstanceLink::from_core(metadata.link),
|
||||
shared_instance: metadata.shared_instance.map(Into::into),
|
||||
quarantined: metadata.quarantined,
|
||||
@@ -414,7 +414,7 @@ fn edit_to_core(edit_instance: EditInstance) -> Result<CoreEditInstance> {
|
||||
name: edit_instance.name,
|
||||
icon_path: None,
|
||||
update_channel: edit_instance.update_channel,
|
||||
groups: edit_instance.groups,
|
||||
group_ids: edit_instance.group_ids,
|
||||
link: edit_instance
|
||||
.link
|
||||
.map(|link| match link {
|
||||
@@ -479,7 +479,8 @@ pub async fn instance_list() -> Result<Vec<Instance>> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_list_groups() -> Result<Vec<theseus::instance::InstanceGroup>> {
|
||||
pub async fn instance_list_groups()
|
||||
-> Result<Vec<theseus::instance::InstanceGroup>> {
|
||||
Ok(theseus::instance::list_groups().await?)
|
||||
}
|
||||
|
||||
@@ -492,15 +493,15 @@ pub async fn instance_create_group(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_rename_group(
|
||||
old_name: String,
|
||||
id: String,
|
||||
new_name: String,
|
||||
) -> Result<theseus::instance::InstanceGroup> {
|
||||
Ok(theseus::instance::rename_group(old_name, new_name).await?)
|
||||
Ok(theseus::instance::rename_group(id, new_name).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_delete_group(name: String) -> Result<()> {
|
||||
Ok(theseus::instance::delete_group(name).await?)
|
||||
pub async fn instance_delete_group(id: String) -> Result<()> {
|
||||
Ok(theseus::instance::delete_group(id).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\tDELETE FROM instance_groups\n\t\tWHERE instance_id = ?\n\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "073e4c6fbf434f94c8015076b50c099d57f217b4c1f4a63761b5e07129eb1f84"
|
||||
}
|
||||
Generated
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\tSELECT group_name\n\t\tFROM instance_groups\n\t\tWHERE instance_id = ?\n\t\tORDER BY group_name\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "group_name",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0812e019f53d81d527ea15cbf7a6416ad15182131cfb6de3f2dab153c74a553c"
|
||||
}
|
||||
Generated
-296
@@ -1,296 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n i.id AS \"id!: String\",\n i.path AS \"path!: String\",\n i.applied_content_set_id AS \"applied_content_set_id?: String\",\n i.install_stage AS \"install_stage!: String\",\n i.launcher_feature_version AS \"launcher_feature_version!: String\",\n i.update_channel AS \"update_channel!: String\",\n i.name AS \"name!: String\",\n i.icon_path AS \"icon_path?: String\",\n i.created AS \"created!: i64\",\n i.modified AS \"modified!: i64\",\n i.last_played AS \"last_played?: i64\",\n i.submitted_time_played AS \"submitted_time_played!: i64\",\n i.recent_time_played AS \"recent_time_played!: i64\",\n cs.id AS \"content_set_id?: String\",\n cs.instance_id AS \"content_set_instance_id?: String\",\n cs.name AS \"content_set_name?: String\",\n cs.source_kind AS \"content_set_source_kind?: String\",\n cs.status AS \"content_set_status?: String\",\n cs.game_version AS \"content_set_game_version?: String\",\n cs.protocol_version AS \"content_set_protocol_version?: i64\",\n cs.loader AS \"content_set_loader?: String\",\n cs.loader_version AS \"content_set_loader_version?: String\",\n cs.created AS \"content_set_created?: i64\",\n cs.modified AS \"content_set_modified?: i64\",\n COALESCE(link.link_kind, 'unmanaged') AS \"link_kind!: String\",\n link.modrinth_project_id AS \"modrinth_project_id?: String\",\n link.modrinth_version_id AS \"modrinth_version_id?: String\",\n link.server_project_id AS \"server_project_id?: String\",\n link.content_project_id AS \"content_project_id?: String\",\n link.content_version_id AS \"content_version_id?: String\",\n link.hosting_server_id AS \"hosting_server_id?: String\",\n json(link.hosting_instance_ids) AS \"hosting_instance_ids?: String\",\n link.hosting_active_instance_id AS \"hosting_active_instance_id?: String\",\n link.shared_instance_id AS \"shared_instance_id?: String\",\n link.shared_instance_role AS \"shared_instance_role?: String\",\n link.shared_instance_manager_id AS \"shared_instance_manager_id?: String\",\n link.shared_instance_server_manager_name AS \"shared_instance_server_manager_name?: String\",\n link.shared_instance_server_manager_icon_url AS \"shared_instance_server_manager_icon_url?: String\",\n link.shared_instance_linked_user_id AS \"shared_instance_linked_user_id?: String\",\n sync.applied_update_id AS \"shared_sync_applied_update_id?: String\",\n sync.latest_available_update_id AS \"shared_sync_latest_available_update_id?: String\",\n sync.status AS \"shared_sync_status?: String\",\n link.imported_name AS \"imported_name?: String\",\n link.imported_version_number AS \"imported_version_number?: String\",\n link.imported_filename AS \"imported_filename?: String\",\n COALESCE((\n SELECT json_group_array(name)\n FROM (\n SELECT groups.name\n FROM instance_group_memberships memberships\n INNER JOIN instance_groups groups\n ON groups.id = memberships.group_id\n WHERE memberships.instance_id = i.id\n ORDER BY groups.name\n )\n ), '[]') AS \"groups!: String\",\n json(overrides.overrides) AS \"launch_overrides?: String\"\n FROM instances i\n LEFT JOIN instance_content_sets cs\n ON cs.id = i.applied_content_set_id\n AND cs.instance_id = i.id\n LEFT JOIN instance_links link\n ON link.instance_id = i.id\n LEFT JOIN instance_content_set_sync_state sync\n ON sync.content_set_id = cs.id\n AND sync.provider = 'shared_instance'\n LEFT JOIN instance_launch_overrides overrides\n ON overrides.instance_id = i.id\n WHERE i.id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!: String",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "path!: String",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "applied_content_set_id?: String",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "install_stage!: String",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "launcher_feature_version!: String",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "update_channel!: String",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "name!: String",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "icon_path?: String",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created!: i64",
|
||||
"ordinal": 8,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "modified!: i64",
|
||||
"ordinal": 9,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "last_played?: i64",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "submitted_time_played!: i64",
|
||||
"ordinal": 11,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "recent_time_played!: i64",
|
||||
"ordinal": 12,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "content_set_id?: String",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_instance_id?: String",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_name?: String",
|
||||
"ordinal": 15,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_source_kind?: String",
|
||||
"ordinal": 16,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_status?: String",
|
||||
"ordinal": 17,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_game_version?: String",
|
||||
"ordinal": 18,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_protocol_version?: i64",
|
||||
"ordinal": 19,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "content_set_loader?: String",
|
||||
"ordinal": 20,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_loader_version?: String",
|
||||
"ordinal": 21,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_created?: i64",
|
||||
"ordinal": 22,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "content_set_modified?: i64",
|
||||
"ordinal": 23,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "link_kind!: String",
|
||||
"ordinal": 24,
|
||||
"type_info": "Null"
|
||||
},
|
||||
{
|
||||
"name": "modrinth_project_id?: String",
|
||||
"ordinal": 25,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "modrinth_version_id?: String",
|
||||
"ordinal": 26,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "server_project_id?: String",
|
||||
"ordinal": 27,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_project_id?: String",
|
||||
"ordinal": 28,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_version_id?: String",
|
||||
"ordinal": 29,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hosting_server_id?: String",
|
||||
"ordinal": 30,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hosting_instance_ids?: String",
|
||||
"ordinal": 31,
|
||||
"type_info": "Null"
|
||||
},
|
||||
{
|
||||
"name": "hosting_active_instance_id?: String",
|
||||
"ordinal": 32,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_id?: String",
|
||||
"ordinal": 33,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_role?: String",
|
||||
"ordinal": 34,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_manager_id?: String",
|
||||
"ordinal": 35,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_server_manager_name?: String",
|
||||
"ordinal": 36,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_server_manager_icon_url?: String",
|
||||
"ordinal": 37,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_linked_user_id?: String",
|
||||
"ordinal": 38,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_sync_applied_update_id?: String",
|
||||
"ordinal": 39,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_sync_latest_available_update_id?: String",
|
||||
"ordinal": 40,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_sync_status?: String",
|
||||
"ordinal": 41,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "imported_name?: String",
|
||||
"ordinal": 42,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "imported_version_number?: String",
|
||||
"ordinal": 43,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "imported_filename?: String",
|
||||
"ordinal": 44,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "groups!: String",
|
||||
"ordinal": 45,
|
||||
"type_info": "Null"
|
||||
},
|
||||
{
|
||||
"name": "launch_overrides?: String",
|
||||
"ordinal": 46,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "16ccee4c11eca353fa347990d6d2af1df5da09605a20a48e3a580d10b6a704ab"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n i.id AS \"id!: String\",\n i.path AS \"path!: String\",\n i.applied_content_set_id AS \"applied_content_set_id?: String\",\n i.install_stage AS \"install_stage!: String\",\n i.launcher_feature_version AS \"launcher_feature_version!: String\",\n i.update_channel AS \"update_channel!: String\",\n i.name AS \"name!: String\",\n i.icon_path AS \"icon_path?: String\",\n i.created AS \"created!: i64\",\n i.modified AS \"modified!: i64\",\n i.last_played AS \"last_played?: i64\",\n i.submitted_time_played AS \"submitted_time_played!: i64\",\n i.recent_time_played AS \"recent_time_played!: i64\",\n cs.id AS \"content_set_id?: String\",\n cs.instance_id AS \"content_set_instance_id?: String\",\n cs.name AS \"content_set_name?: String\",\n cs.source_kind AS \"content_set_source_kind?: String\",\n cs.status AS \"content_set_status?: String\",\n cs.game_version AS \"content_set_game_version?: String\",\n cs.protocol_version AS \"content_set_protocol_version?: i64\",\n cs.loader AS \"content_set_loader?: String\",\n cs.loader_version AS \"content_set_loader_version?: String\",\n cs.created AS \"content_set_created?: i64\",\n cs.modified AS \"content_set_modified?: i64\",\n COALESCE(link.link_kind, 'unmanaged') AS \"link_kind!: String\",\n link.modrinth_project_id AS \"modrinth_project_id?: String\",\n link.modrinth_version_id AS \"modrinth_version_id?: String\",\n link.server_project_id AS \"server_project_id?: String\",\n link.content_project_id AS \"content_project_id?: String\",\n link.content_version_id AS \"content_version_id?: String\",\n link.hosting_server_id AS \"hosting_server_id?: String\",\n json(link.hosting_instance_ids) AS \"hosting_instance_ids?: String\",\n link.hosting_active_instance_id AS \"hosting_active_instance_id?: String\",\n link.shared_instance_id AS \"shared_instance_id?: String\",\n link.shared_instance_role AS \"shared_instance_role?: String\",\n link.shared_instance_manager_id AS \"shared_instance_manager_id?: String\",\n link.shared_instance_server_manager_name AS \"shared_instance_server_manager_name?: String\",\n link.shared_instance_server_manager_icon_url AS \"shared_instance_server_manager_icon_url?: String\",\n link.shared_instance_linked_user_id AS \"shared_instance_linked_user_id?: String\",\n sync.applied_update_id AS \"shared_sync_applied_update_id?: String\",\n sync.latest_available_update_id AS \"shared_sync_latest_available_update_id?: String\",\n sync.status AS \"shared_sync_status?: String\",\n link.imported_name AS \"imported_name?: String\",\n link.imported_version_number AS \"imported_version_number?: String\",\n link.imported_filename AS \"imported_filename?: String\",\n COALESCE((\n SELECT json_group_array(group_name)\n FROM (\n SELECT group_name\n FROM instance_groups\n WHERE instance_id = i.id\n ORDER BY group_name\n )\n ), '[]') AS \"groups!: String\",\n json(overrides.overrides) AS \"launch_overrides?: String\"\n FROM instances i\n LEFT JOIN instance_content_sets cs\n ON cs.id = i.applied_content_set_id\n AND cs.instance_id = i.id\n LEFT JOIN instance_links link\n ON link.instance_id = i.id\n LEFT JOIN instance_content_set_sync_state sync\n ON sync.content_set_id = cs.id\n AND sync.provider = 'shared_instance'\n LEFT JOIN instance_launch_overrides overrides\n ON overrides.instance_id = i.id\n WHERE i.id = ?",
|
||||
"query": "\n SELECT\n i.id AS \"id!: String\",\n i.path AS \"path!: String\",\n i.applied_content_set_id AS \"applied_content_set_id?: String\",\n i.install_stage AS \"install_stage!: String\",\n i.launcher_feature_version AS \"launcher_feature_version!: String\",\n i.update_channel AS \"update_channel!: String\",\n i.name AS \"name!: String\",\n i.icon_path AS \"icon_path?: String\",\n i.created AS \"created!: i64\",\n i.modified AS \"modified!: i64\",\n i.last_played AS \"last_played?: i64\",\n i.submitted_time_played AS \"submitted_time_played!: i64\",\n i.recent_time_played AS \"recent_time_played!: i64\",\n cs.id AS \"content_set_id?: String\",\n cs.instance_id AS \"content_set_instance_id?: String\",\n cs.name AS \"content_set_name?: String\",\n cs.source_kind AS \"content_set_source_kind?: String\",\n cs.status AS \"content_set_status?: String\",\n cs.game_version AS \"content_set_game_version?: String\",\n cs.protocol_version AS \"content_set_protocol_version?: i64\",\n cs.loader AS \"content_set_loader?: String\",\n cs.loader_version AS \"content_set_loader_version?: String\",\n cs.created AS \"content_set_created?: i64\",\n cs.modified AS \"content_set_modified?: i64\",\n COALESCE(link.link_kind, 'unmanaged') AS \"link_kind!: String\",\n link.modrinth_project_id AS \"modrinth_project_id?: String\",\n link.modrinth_version_id AS \"modrinth_version_id?: String\",\n link.server_project_id AS \"server_project_id?: String\",\n link.content_project_id AS \"content_project_id?: String\",\n link.content_version_id AS \"content_version_id?: String\",\n link.hosting_server_id AS \"hosting_server_id?: String\",\n json(link.hosting_instance_ids) AS \"hosting_instance_ids?: String\",\n link.hosting_active_instance_id AS \"hosting_active_instance_id?: String\",\n link.shared_instance_id AS \"shared_instance_id?: String\",\n link.shared_instance_role AS \"shared_instance_role?: String\",\n link.shared_instance_manager_id AS \"shared_instance_manager_id?: String\",\n link.shared_instance_server_manager_name AS \"shared_instance_server_manager_name?: String\",\n link.shared_instance_server_manager_icon_url AS \"shared_instance_server_manager_icon_url?: String\",\n link.shared_instance_linked_user_id AS \"shared_instance_linked_user_id?: String\",\n sync.applied_update_id AS \"shared_sync_applied_update_id?: String\",\n sync.latest_available_update_id AS \"shared_sync_latest_available_update_id?: String\",\n sync.status AS \"shared_sync_status?: String\",\n link.imported_name AS \"imported_name?: String\",\n link.imported_version_number AS \"imported_version_number?: String\",\n link.imported_filename AS \"imported_filename?: String\",\n COALESCE((\n SELECT json_group_array(id)\n FROM (\n SELECT groups.id\n FROM instance_group_memberships memberships\n INNER JOIN instance_groups groups\n ON groups.id = memberships.group_id\n WHERE memberships.instance_id = i.id\n ORDER BY groups.name\n )\n ), '[]') AS \"group_ids!: String\",\n json(overrides.overrides) AS \"launch_overrides?: String\"\n FROM instances i\n LEFT JOIN instance_content_sets cs\n ON cs.id = i.applied_content_set_id\n AND cs.instance_id = i.id\n LEFT JOIN instance_links link\n ON link.instance_id = i.id\n LEFT JOIN instance_content_set_sync_state sync\n ON sync.content_set_id = cs.id\n AND sync.provider = 'shared_instance'\n LEFT JOIN instance_launch_overrides overrides\n ON overrides.instance_id = i.id\n WHERE i.id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -229,7 +229,7 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "groups!: String",
|
||||
"name": "group_ids!: String",
|
||||
"ordinal": 45,
|
||||
"type_info": "Null"
|
||||
},
|
||||
@@ -292,5 +292,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "0a708a6410e4d7d4cbdb07fa6ea32383be454526177686a0685a988af747db0f"
|
||||
"hash": "2171aca7d44d86f0e0ec5bf1dec37da14fc8bafa72fdf2b589663c15cb047a68"
|
||||
}
|
||||
Generated
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\tSELECT groups.id\n\t\tFROM instance_group_memberships memberships\n\t\tINNER JOIN instance_groups groups\n\t\t\tON groups.id = memberships.group_id\n\t\tWHERE memberships.instance_id = ?\n\t\tORDER BY groups.name\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "73a5b7a4b61a75705f6ed3ea370ec66677b42cdd09b8ae87cee0ef89d3875681"
|
||||
}
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT OR IGNORE INTO instance_groups (instance_id, group_name)\n VALUES (?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7790039c6bfeba24c9a0f515b12d06e8ca910f05a73f2b31fe80eef9033873d7"
|
||||
}
|
||||
+3
-3
File diff suppressed because one or more lines are too long
Generated
-296
File diff suppressed because one or more lines are too long
Generated
-296
@@ -1,296 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n i.id AS \"id!: String\",\n i.path AS \"path!: String\",\n i.applied_content_set_id AS \"applied_content_set_id?: String\",\n i.install_stage AS \"install_stage!: String\",\n i.launcher_feature_version AS \"launcher_feature_version!: String\",\n i.update_channel AS \"update_channel!: String\",\n i.name AS \"name!: String\",\n i.icon_path AS \"icon_path?: String\",\n i.created AS \"created!: i64\",\n i.modified AS \"modified!: i64\",\n i.last_played AS \"last_played?: i64\",\n i.submitted_time_played AS \"submitted_time_played!: i64\",\n i.recent_time_played AS \"recent_time_played!: i64\",\n cs.id AS \"content_set_id?: String\",\n cs.instance_id AS \"content_set_instance_id?: String\",\n cs.name AS \"content_set_name?: String\",\n cs.source_kind AS \"content_set_source_kind?: String\",\n cs.status AS \"content_set_status?: String\",\n cs.game_version AS \"content_set_game_version?: String\",\n cs.protocol_version AS \"content_set_protocol_version?: i64\",\n cs.loader AS \"content_set_loader?: String\",\n cs.loader_version AS \"content_set_loader_version?: String\",\n cs.created AS \"content_set_created?: i64\",\n cs.modified AS \"content_set_modified?: i64\",\n COALESCE(link.link_kind, 'unmanaged') AS \"link_kind!: String\",\n link.modrinth_project_id AS \"modrinth_project_id?: String\",\n link.modrinth_version_id AS \"modrinth_version_id?: String\",\n link.server_project_id AS \"server_project_id?: String\",\n link.content_project_id AS \"content_project_id?: String\",\n link.content_version_id AS \"content_version_id?: String\",\n link.hosting_server_id AS \"hosting_server_id?: String\",\n json(link.hosting_instance_ids) AS \"hosting_instance_ids?: String\",\n link.hosting_active_instance_id AS \"hosting_active_instance_id?: String\",\n link.shared_instance_id AS \"shared_instance_id?: String\",\n link.shared_instance_role AS \"shared_instance_role?: String\",\n link.shared_instance_manager_id AS \"shared_instance_manager_id?: String\",\n link.shared_instance_server_manager_name AS \"shared_instance_server_manager_name?: String\",\n link.shared_instance_server_manager_icon_url AS \"shared_instance_server_manager_icon_url?: String\",\n link.shared_instance_linked_user_id AS \"shared_instance_linked_user_id?: String\",\n sync.applied_update_id AS \"shared_sync_applied_update_id?: String\",\n sync.latest_available_update_id AS \"shared_sync_latest_available_update_id?: String\",\n sync.status AS \"shared_sync_status?: String\",\n link.imported_name AS \"imported_name?: String\",\n link.imported_version_number AS \"imported_version_number?: String\",\n link.imported_filename AS \"imported_filename?: String\",\n COALESCE((\n SELECT json_group_array(group_name)\n FROM (\n SELECT group_name\n FROM instance_groups\n WHERE instance_id = i.id\n ORDER BY group_name\n )\n ), '[]') AS \"groups!: String\",\n json(overrides.overrides) AS \"launch_overrides?: String\"\n FROM instances i\n LEFT JOIN instance_content_sets cs\n ON cs.id = i.applied_content_set_id\n AND cs.instance_id = i.id\n LEFT JOIN instance_links link\n ON link.instance_id = i.id\n LEFT JOIN instance_content_set_sync_state sync\n ON sync.content_set_id = cs.id\n AND sync.provider = 'shared_instance'\n LEFT JOIN instance_launch_overrides overrides\n ON overrides.instance_id = i.id\n WHERE 1 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!: String",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "path!: String",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "applied_content_set_id?: String",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "install_stage!: String",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "launcher_feature_version!: String",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "update_channel!: String",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "name!: String",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "icon_path?: String",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created!: i64",
|
||||
"ordinal": 8,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "modified!: i64",
|
||||
"ordinal": 9,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "last_played?: i64",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "submitted_time_played!: i64",
|
||||
"ordinal": 11,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "recent_time_played!: i64",
|
||||
"ordinal": 12,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "content_set_id?: String",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_instance_id?: String",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_name?: String",
|
||||
"ordinal": 15,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_source_kind?: String",
|
||||
"ordinal": 16,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_status?: String",
|
||||
"ordinal": 17,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_game_version?: String",
|
||||
"ordinal": 18,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_protocol_version?: i64",
|
||||
"ordinal": 19,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "content_set_loader?: String",
|
||||
"ordinal": 20,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_loader_version?: String",
|
||||
"ordinal": 21,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_set_created?: i64",
|
||||
"ordinal": 22,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "content_set_modified?: i64",
|
||||
"ordinal": 23,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "link_kind!: String",
|
||||
"ordinal": 24,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "modrinth_project_id?: String",
|
||||
"ordinal": 25,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "modrinth_version_id?: String",
|
||||
"ordinal": 26,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "server_project_id?: String",
|
||||
"ordinal": 27,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_project_id?: String",
|
||||
"ordinal": 28,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_version_id?: String",
|
||||
"ordinal": 29,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hosting_server_id?: String",
|
||||
"ordinal": 30,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "hosting_instance_ids?: String",
|
||||
"ordinal": 31,
|
||||
"type_info": "Null"
|
||||
},
|
||||
{
|
||||
"name": "hosting_active_instance_id?: String",
|
||||
"ordinal": 32,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_id?: String",
|
||||
"ordinal": 33,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_role?: String",
|
||||
"ordinal": 34,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_manager_id?: String",
|
||||
"ordinal": 35,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_server_manager_name?: String",
|
||||
"ordinal": 36,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_server_manager_icon_url?: String",
|
||||
"ordinal": 37,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_instance_linked_user_id?: String",
|
||||
"ordinal": 38,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_sync_applied_update_id?: String",
|
||||
"ordinal": 39,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_sync_latest_available_update_id?: String",
|
||||
"ordinal": 40,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "shared_sync_status?: String",
|
||||
"ordinal": 41,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "imported_name?: String",
|
||||
"ordinal": 42,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "imported_version_number?: String",
|
||||
"ordinal": 43,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "imported_filename?: String",
|
||||
"ordinal": 44,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "groups!: String",
|
||||
"ordinal": 45,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "launch_overrides?: String",
|
||||
"ordinal": 46,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "aaf6e6a3de2dd9cb1c9db5dd9de2a6590b89c4f72449fdd34a7f140e2a11bf04"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n i.id AS \"id!: String\",\n i.path AS \"path!: String\",\n i.applied_content_set_id AS \"applied_content_set_id?: String\",\n i.install_stage AS \"install_stage!: String\",\n i.launcher_feature_version AS \"launcher_feature_version!: String\",\n i.update_channel AS \"update_channel!: String\",\n i.name AS \"name!: String\",\n i.icon_path AS \"icon_path?: String\",\n i.created AS \"created!: i64\",\n i.modified AS \"modified!: i64\",\n i.last_played AS \"last_played?: i64\",\n i.submitted_time_played AS \"submitted_time_played!: i64\",\n i.recent_time_played AS \"recent_time_played!: i64\",\n cs.id AS \"content_set_id?: String\",\n cs.instance_id AS \"content_set_instance_id?: String\",\n cs.name AS \"content_set_name?: String\",\n cs.source_kind AS \"content_set_source_kind?: String\",\n cs.status AS \"content_set_status?: String\",\n cs.game_version AS \"content_set_game_version?: String\",\n cs.protocol_version AS \"content_set_protocol_version?: i64\",\n cs.loader AS \"content_set_loader?: String\",\n cs.loader_version AS \"content_set_loader_version?: String\",\n cs.created AS \"content_set_created?: i64\",\n cs.modified AS \"content_set_modified?: i64\",\n COALESCE(link.link_kind, 'unmanaged') AS \"link_kind!: String\",\n link.modrinth_project_id AS \"modrinth_project_id?: String\",\n link.modrinth_version_id AS \"modrinth_version_id?: String\",\n link.server_project_id AS \"server_project_id?: String\",\n link.content_project_id AS \"content_project_id?: String\",\n link.content_version_id AS \"content_version_id?: String\",\n link.hosting_server_id AS \"hosting_server_id?: String\",\n json(link.hosting_instance_ids) AS \"hosting_instance_ids?: String\",\n link.hosting_active_instance_id AS \"hosting_active_instance_id?: String\",\n link.shared_instance_id AS \"shared_instance_id?: String\",\n link.shared_instance_role AS \"shared_instance_role?: String\",\n link.shared_instance_manager_id AS \"shared_instance_manager_id?: String\",\n link.shared_instance_server_manager_name AS \"shared_instance_server_manager_name?: String\",\n link.shared_instance_server_manager_icon_url AS \"shared_instance_server_manager_icon_url?: String\",\n link.shared_instance_linked_user_id AS \"shared_instance_linked_user_id?: String\",\n sync.applied_update_id AS \"shared_sync_applied_update_id?: String\",\n sync.latest_available_update_id AS \"shared_sync_latest_available_update_id?: String\",\n sync.status AS \"shared_sync_status?: String\",\n link.imported_name AS \"imported_name?: String\",\n link.imported_version_number AS \"imported_version_number?: String\",\n link.imported_filename AS \"imported_filename?: String\",\n COALESCE((\n SELECT json_group_array(name)\n FROM (\n SELECT groups.name\n FROM instance_group_memberships memberships\n INNER JOIN instance_groups groups\n ON groups.id = memberships.group_id\n WHERE memberships.instance_id = i.id\n ORDER BY groups.name\n )\n ), '[]') AS \"groups!: String\",\n json(overrides.overrides) AS \"launch_overrides?: String\"\n FROM instances i\n LEFT JOIN instance_content_sets cs\n ON cs.id = i.applied_content_set_id\n AND cs.instance_id = i.id\n LEFT JOIN instance_links link\n ON link.instance_id = i.id\n LEFT JOIN instance_content_set_sync_state sync\n ON sync.content_set_id = cs.id\n AND sync.provider = 'shared_instance'\n LEFT JOIN instance_launch_overrides overrides\n ON overrides.instance_id = i.id\n WHERE 1 = ?",
|
||||
"query": "\n SELECT\n i.id AS \"id!: String\",\n i.path AS \"path!: String\",\n i.applied_content_set_id AS \"applied_content_set_id?: String\",\n i.install_stage AS \"install_stage!: String\",\n i.launcher_feature_version AS \"launcher_feature_version!: String\",\n i.update_channel AS \"update_channel!: String\",\n i.name AS \"name!: String\",\n i.icon_path AS \"icon_path?: String\",\n i.created AS \"created!: i64\",\n i.modified AS \"modified!: i64\",\n i.last_played AS \"last_played?: i64\",\n i.submitted_time_played AS \"submitted_time_played!: i64\",\n i.recent_time_played AS \"recent_time_played!: i64\",\n cs.id AS \"content_set_id?: String\",\n cs.instance_id AS \"content_set_instance_id?: String\",\n cs.name AS \"content_set_name?: String\",\n cs.source_kind AS \"content_set_source_kind?: String\",\n cs.status AS \"content_set_status?: String\",\n cs.game_version AS \"content_set_game_version?: String\",\n cs.protocol_version AS \"content_set_protocol_version?: i64\",\n cs.loader AS \"content_set_loader?: String\",\n cs.loader_version AS \"content_set_loader_version?: String\",\n cs.created AS \"content_set_created?: i64\",\n cs.modified AS \"content_set_modified?: i64\",\n COALESCE(link.link_kind, 'unmanaged') AS \"link_kind!: String\",\n link.modrinth_project_id AS \"modrinth_project_id?: String\",\n link.modrinth_version_id AS \"modrinth_version_id?: String\",\n link.server_project_id AS \"server_project_id?: String\",\n link.content_project_id AS \"content_project_id?: String\",\n link.content_version_id AS \"content_version_id?: String\",\n link.hosting_server_id AS \"hosting_server_id?: String\",\n json(link.hosting_instance_ids) AS \"hosting_instance_ids?: String\",\n link.hosting_active_instance_id AS \"hosting_active_instance_id?: String\",\n link.shared_instance_id AS \"shared_instance_id?: String\",\n link.shared_instance_role AS \"shared_instance_role?: String\",\n link.shared_instance_manager_id AS \"shared_instance_manager_id?: String\",\n link.shared_instance_server_manager_name AS \"shared_instance_server_manager_name?: String\",\n link.shared_instance_server_manager_icon_url AS \"shared_instance_server_manager_icon_url?: String\",\n link.shared_instance_linked_user_id AS \"shared_instance_linked_user_id?: String\",\n sync.applied_update_id AS \"shared_sync_applied_update_id?: String\",\n sync.latest_available_update_id AS \"shared_sync_latest_available_update_id?: String\",\n sync.status AS \"shared_sync_status?: String\",\n link.imported_name AS \"imported_name?: String\",\n link.imported_version_number AS \"imported_version_number?: String\",\n link.imported_filename AS \"imported_filename?: String\",\n COALESCE((\n SELECT json_group_array(id)\n FROM (\n SELECT groups.id\n FROM instance_group_memberships memberships\n INNER JOIN instance_groups groups\n ON groups.id = memberships.group_id\n WHERE memberships.instance_id = i.id\n ORDER BY groups.name\n )\n ), '[]') AS \"group_ids!: String\",\n json(overrides.overrides) AS \"launch_overrides?: String\"\n FROM instances i\n LEFT JOIN instance_content_sets cs\n ON cs.id = i.applied_content_set_id\n AND cs.instance_id = i.id\n LEFT JOIN instance_links link\n ON link.instance_id = i.id\n LEFT JOIN instance_content_set_sync_state sync\n ON sync.content_set_id = cs.id\n AND sync.provider = 'shared_instance'\n LEFT JOIN instance_launch_overrides overrides\n ON overrides.instance_id = i.id\n WHERE 1 = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -229,7 +229,7 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "groups!: String",
|
||||
"name": "group_ids!: String",
|
||||
"ordinal": 45,
|
||||
"type_info": "Text"
|
||||
},
|
||||
@@ -292,5 +292,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "3b9ff6d0c4dfa410b343bf7f4e01cad57a56093c1950027c190ca8e751bd0ca2"
|
||||
"hash": "c4c754b4682e5132c3ac07764082c8dbf64f1633a8a00d925e66d005de29dd3d"
|
||||
}
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\t\tINSERT OR IGNORE INTO instance_groups (instance_id, group_name)\n\t\t\tVALUES (?, ?)\n\t\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e26d2ce1fdaaef4ef4648b57faf0ea2940ba9ada7b39f3c2fa973a36ee4cba2a"
|
||||
}
|
||||
Generated
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n\t\tSELECT groups.name\n\t\tFROM instance_group_memberships memberships\n\t\tINNER JOIN instance_groups groups\n\t\t\tON groups.id = memberships.group_id\n\t\tWHERE memberships.instance_id = ?\n\t\tORDER BY groups.name\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "name",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ff28a1341b04afc4af46cd7110cb1daa7a669b3e7363fc27bbf4e66e82c98413"
|
||||
}
|
||||
@@ -4,8 +4,7 @@ CREATE TABLE instance_groups (
|
||||
id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (name)
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE instance_group_memberships (
|
||||
@@ -31,6 +30,22 @@ SELECT
|
||||
FROM legacy_instance_group_memberships legacy
|
||||
INNER JOIN instance_groups groups ON groups.name = legacy.group_name;
|
||||
|
||||
UPDATE install_jobs
|
||||
SET state = json_remove(
|
||||
json_set(
|
||||
state,
|
||||
'$.rollback.instance.group_ids',
|
||||
json(COALESCE((
|
||||
SELECT json_group_array(groups.id)
|
||||
FROM json_each(state, '$.rollback.instance.groups') legacy_groups
|
||||
INNER JOIN instance_groups groups
|
||||
ON groups.name = legacy_groups.value
|
||||
), '[]'))
|
||||
),
|
||||
'$.rollback.instance.groups'
|
||||
)
|
||||
WHERE json_type(state, '$.rollback.instance.groups') = 'array';
|
||||
|
||||
DROP TABLE legacy_instance_group_memberships;
|
||||
|
||||
CREATE INDEX instance_groups_name ON instance_groups(name);
|
||||
|
||||
@@ -52,24 +52,6 @@ pub async fn list_groups() -> crate::Result<Vec<InstanceGroup>> {
|
||||
pub async fn create_group(name: String) -> crate::Result<InstanceGroup> {
|
||||
let name = validate_group_name(&name)?;
|
||||
let state = State::get().await?;
|
||||
let existing_group = sqlx::query_scalar::<_, String>(
|
||||
"
|
||||
SELECT name
|
||||
FROM instance_groups
|
||||
WHERE lower(name) = lower(?)
|
||||
",
|
||||
)
|
||||
.bind(name)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
if existing_group.is_some() {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"A group with this name already exists".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4().to_string();
|
||||
instance_rows::create_instance_group(&id, name, &state.pool).await?;
|
||||
|
||||
@@ -80,47 +62,12 @@ pub async fn create_group(name: String) -> crate::Result<InstanceGroup> {
|
||||
}
|
||||
|
||||
pub async fn rename_group(
|
||||
old_name: String,
|
||||
id: String,
|
||||
new_name: String,
|
||||
) -> crate::Result<InstanceGroup> {
|
||||
let new_name = validate_group_name(&new_name)?;
|
||||
let state = State::get().await?;
|
||||
let mut tx = state.pool.begin().await?;
|
||||
let group_id = sqlx::query_scalar::<_, String>(
|
||||
"
|
||||
SELECT id
|
||||
FROM instance_groups
|
||||
WHERE name = ?
|
||||
",
|
||||
)
|
||||
.bind(&old_name)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(format!(
|
||||
"Unknown instance group {old_name}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
let existing_group = sqlx::query_scalar::<_, String>(
|
||||
"
|
||||
SELECT id
|
||||
FROM instance_groups
|
||||
WHERE lower(name) = lower(?)
|
||||
AND id != ?
|
||||
",
|
||||
)
|
||||
.bind(new_name)
|
||||
.bind(&group_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if existing_group.is_some() {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"A group with this name already exists".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let instance_ids = sqlx::query_scalar::<_, String>(
|
||||
"
|
||||
@@ -129,11 +76,11 @@ pub async fn rename_group(
|
||||
WHERE group_id = ?
|
||||
",
|
||||
)
|
||||
.bind(&group_id)
|
||||
.bind(&id)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
let result = sqlx::query(
|
||||
"
|
||||
UPDATE instance_groups
|
||||
SET name = ?
|
||||
@@ -141,10 +88,17 @@ pub async fn rename_group(
|
||||
",
|
||||
)
|
||||
.bind(new_name)
|
||||
.bind(&group_id)
|
||||
.bind(&id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(crate::ErrorKind::InputError(format!(
|
||||
"Unknown instance group {id}"
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
for instance_id in instance_ids {
|
||||
@@ -152,40 +106,38 @@ pub async fn rename_group(
|
||||
}
|
||||
|
||||
Ok(InstanceGroup {
|
||||
id: group_id,
|
||||
id,
|
||||
name: new_name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete_group(name: String) -> crate::Result<()> {
|
||||
pub async fn delete_group(id: String) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut tx = state.pool.begin().await?;
|
||||
let instance_ids = sqlx::query_scalar::<_, String>(
|
||||
"
|
||||
SELECT memberships.instance_id
|
||||
FROM instance_group_memberships memberships
|
||||
INNER JOIN instance_groups groups
|
||||
ON groups.id = memberships.group_id
|
||||
WHERE groups.name = ?
|
||||
SELECT instance_id
|
||||
FROM instance_group_memberships
|
||||
WHERE group_id = ?
|
||||
",
|
||||
)
|
||||
.bind(&name)
|
||||
.bind(&id)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
let result = sqlx::query(
|
||||
"
|
||||
DELETE FROM instance_groups
|
||||
WHERE name = ?
|
||||
WHERE id = ?
|
||||
",
|
||||
)
|
||||
.bind(&name)
|
||||
.bind(&id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(crate::ErrorKind::InputError(format!(
|
||||
"Unknown instance group {name}"
|
||||
"Unknown instance group {id}"
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ async fn restore_instance_metadata(
|
||||
.await?;
|
||||
instance_rows::replace_instance_groups(
|
||||
&metadata.instance.id,
|
||||
&metadata.groups,
|
||||
&metadata.group_ids,
|
||||
&mut tx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -164,7 +164,7 @@ pub(crate) struct InstanceMetadataRecord {
|
||||
pub applied_content_set: ContentSet,
|
||||
pub link: InstanceLink,
|
||||
pub shared_instance: Option<SharedInstanceAttachment>,
|
||||
pub groups: Vec<String>,
|
||||
pub group_ids: Vec<String>,
|
||||
pub launch_overrides: InstanceLaunchOverrides,
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ struct InstanceMetadataRow {
|
||||
imported_name: Option<String>,
|
||||
imported_version_number: Option<String>,
|
||||
imported_filename: Option<String>,
|
||||
groups: String,
|
||||
group_ids: String,
|
||||
launch_overrides: Option<String>,
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ impl InstanceMetadataRow {
|
||||
self.shared_sync_applied_update_id,
|
||||
self.shared_sync_latest_available_update_id,
|
||||
)?;
|
||||
let groups = parse_groups(self.groups)?;
|
||||
let group_ids = parse_group_ids(self.group_ids)?;
|
||||
let launch_overrides =
|
||||
launch_overrides_from_json(instance_id, self.launch_overrides)?;
|
||||
|
||||
@@ -342,7 +342,7 @@ impl InstanceMetadataRow {
|
||||
applied_content_set,
|
||||
link,
|
||||
shared_instance,
|
||||
groups,
|
||||
group_ids,
|
||||
launch_overrides,
|
||||
})
|
||||
}
|
||||
@@ -576,16 +576,16 @@ macro_rules! query_instance_metadata {
|
||||
link.imported_version_number AS "imported_version_number?: String",
|
||||
link.imported_filename AS "imported_filename?: String",
|
||||
COALESCE((
|
||||
SELECT json_group_array(name)
|
||||
SELECT json_group_array(id)
|
||||
FROM (
|
||||
SELECT groups.name
|
||||
SELECT groups.id
|
||||
FROM instance_group_memberships memberships
|
||||
INNER JOIN instance_groups groups
|
||||
ON groups.id = memberships.group_id
|
||||
WHERE memberships.instance_id = i.id
|
||||
ORDER BY groups.name
|
||||
)
|
||||
), '[]') AS "groups!: String",
|
||||
), '[]') AS "group_ids!: String",
|
||||
json(overrides.overrides) AS "launch_overrides?: String"
|
||||
"#
|
||||
+ $from
|
||||
@@ -748,7 +748,7 @@ where
|
||||
{
|
||||
let rows = sqlx::query_scalar!(
|
||||
"
|
||||
SELECT groups.name
|
||||
SELECT groups.id
|
||||
FROM instance_group_memberships memberships
|
||||
INNER JOIN instance_groups groups
|
||||
ON groups.id = memberships.group_id
|
||||
@@ -770,7 +770,7 @@ pub(crate) async fn list_instance_groups(
|
||||
"
|
||||
SELECT id, name
|
||||
FROM instance_groups
|
||||
ORDER BY name
|
||||
ORDER BY name, id
|
||||
",
|
||||
)
|
||||
.fetch_all(pool)
|
||||
@@ -1071,7 +1071,7 @@ pub(crate) async fn set_shared_instance_attachment(
|
||||
|
||||
pub(crate) async fn replace_instance_groups(
|
||||
instance_id: &str,
|
||||
groups: &[String],
|
||||
group_ids: &[String],
|
||||
tx: &mut Transaction<'_, Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
sqlx::query!(
|
||||
@@ -1084,35 +1084,7 @@ pub(crate) async fn replace_instance_groups(
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
for group in groups {
|
||||
let group_id = match sqlx::query_scalar::<_, String>(
|
||||
"
|
||||
SELECT id
|
||||
FROM instance_groups
|
||||
WHERE name = ?
|
||||
",
|
||||
)
|
||||
.bind(group)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await?
|
||||
{
|
||||
Some(group_id) => group_id,
|
||||
None => {
|
||||
let group_id = Uuid::new_v4().to_string();
|
||||
sqlx::query(
|
||||
"
|
||||
INSERT INTO instance_groups (id, name)
|
||||
VALUES (?, ?)
|
||||
",
|
||||
)
|
||||
.bind(&group_id)
|
||||
.bind(group)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
group_id
|
||||
}
|
||||
};
|
||||
|
||||
for group_id in group_ids {
|
||||
sqlx::query(
|
||||
"
|
||||
INSERT OR IGNORE INTO instance_group_memberships (
|
||||
@@ -1334,7 +1306,7 @@ fn required_i64(value: Option<i64>, column: &str) -> crate::Result<i64> {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_groups(value: String) -> crate::Result<Vec<String>> {
|
||||
fn parse_group_ids(value: String) -> crate::Result<Vec<String>> {
|
||||
serde_json::from_str(&value).map_err(|err| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Invalid instance groups JSON: {err}"
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct EditInstance {
|
||||
)]
|
||||
pub icon_path: Option<Option<String>>,
|
||||
pub update_channel: Option<ReleaseChannel>,
|
||||
pub groups: Option<Vec<String>>,
|
||||
pub group_ids: Option<Vec<String>>,
|
||||
pub link: Option<InstanceLink>,
|
||||
pub launch_overrides: Option<InstanceLaunchOverridesPatch>,
|
||||
pub content_set_patch: Option<AppliedContentSetPatch>,
|
||||
@@ -174,9 +174,13 @@ pub(crate) async fn edit_instance(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(groups) = &patch.groups {
|
||||
instance_rows::replace_instance_groups(&instance.id, groups, &mut tx)
|
||||
.await?;
|
||||
if let Some(group_ids) = &patch.group_ids {
|
||||
instance_rows::replace_instance_groups(
|
||||
&instance.id,
|
||||
group_ids,
|
||||
&mut tx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(overrides) = launch_overrides.as_mut() {
|
||||
|
||||
@@ -13,7 +13,7 @@ pub struct InstanceMetadata {
|
||||
pub shared_instance: Option<SharedInstanceAttachment>,
|
||||
#[serde(default)]
|
||||
pub quarantined: bool,
|
||||
pub groups: Vec<String>,
|
||||
pub group_ids: Vec<String>,
|
||||
pub launch_overrides: InstanceLaunchOverrides,
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ fn instance_metadata(
|
||||
link: record.link,
|
||||
shared_instance: record.shared_instance,
|
||||
quarantined,
|
||||
groups: record.groups,
|
||||
group_ids: record.group_ids,
|
||||
launch_overrides: record.launch_overrides,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user