{"version":3,"file":"FavoritesView-DBX4SG_Y.js","sources":["../../../ui/src/components/project-catalog/ProjectCatalogTags.vue","../../../ui/src/components/project-catalog/ProjectCatalogGridCard.vue","../../../ui/src/components/project-catalog/ProjectCatalogListColumns.vue","../../../ui/src/components/project-catalog/ProjectCatalogListItem.vue","../../../ui/src/components/project-catalog/ProjectCatalogList.vue","../../../ui/src/components/Pagination.vue","../../../ui/src/composables/list-grid-view-preference.ts","../../../ui/src/components/project-catalog/ProjectCatalog.vue","../../../ui/src/components/SearchProjectsCollapsible.vue","../../../ui/src/components/SortDropdown.vue","../../../ui/src/components/ListGridToggle.vue","../../../ui/src/composables/paginated-project-list.ts","../../../ui/src/components/ItemsPerPageDropdown.vue","../../../ui/src/components/buttons/NewProjectButton.vue","../../../ui/src/views/FavoritesView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport { useProjectSettingsModalStore } from '~/stores/project-settings-modal-store';\nimport { useUserStore } from '~/stores/user-store';\nimport type { Project } from '~/utilities/pyscript-api-models';\n\nconst props = defineProps<{\n project: Project;\n clickHandler: (...args: any) => void;\n}>();\n\nconst { t } = useI18n();\nconst userStore = useUserStore();\nconst projectSettingsModalStore = useProjectSettingsModalStore();\nconst isProjectOwner = computed(() => userStore.isProjectOwner(props.project));\n</script>\n\n<template>\n <div class=\"relative flex h-5 flex-nowrap gap-2 truncate\">\n <button\n v-if=\"project.tags.length === 0 && isProjectOwner\"\n class=\"pointer-events-auto relative max-w-full whitespace-nowrap rounded-full bg-new-gray-100/60 px-2 py-0.5 text-xs font-normal text-gray-700 transition-colors hover:bg-new-gray-100 dark:bg-new-gray-300 hover:dark:bg-new-gray-400\"\n @click=\"projectSettingsModalStore.open(props.project, 0)\"\n >\n {{ t('buttons.add_tag') }}\n </button>\n\n <button\n v-for=\"tag in project.tags\"\n v-else\n :key=\"tag\"\n class=\"pointer-events-auto max-w-full whitespace-nowrap rounded-full bg-new-green-600/80 px-2 py-0.5 text-xs font-normal text-gray-800 hover:bg-new-green-700 dark:bg-new-green-700 hover:dark:bg-new-green-600\"\n @click=\"clickHandler(tag)\"\n >\n {{ tag }}\n </button>\n\n <!-- Faded gradient for overflowed items -->\n <div\n class=\"pointer-events-none absolute bottom-0 right-0 top-0 w-4 bg-gradient-to-l from-white from-10% group-hover:from-new-gray-50 dark:from-gray-675 group-hover:dark:from-new-gray-900\"\n />\n </div>\n</template>\n\n<style scoped lang=\"postcss\"></style>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue';\nimport { formatTimeAgo } from '@vueuse/core';\nimport type { Project } from '~/utilities/pyscript-api-models';\nimport { toProjectPage } from '~/utilities/to-project-page';\nimport { useUserStore } from '~/stores/user-store';\nimport { useProjectSettingsModalStore } from '~/stores/project-settings-modal-store';\nimport ProjectThreeDotMenu from '~/components/ProjectThreeDotMenu.vue';\nimport ProjectCatalogTags from '~/components/project-catalog/ProjectCatalogTags.vue';\nimport FavoriteButton from '~/components/buttons/FavoriteButton.vue';\nimport IconLock from '~icons/material-symbols/lock';\nimport IconExternalLink from '~icons/carbon/launch';\n\nconst props = defineProps<{\n project: Project;\n tagClickHandler: (...args: any) => void;\n}>();\n\ndefineEmits<{\n (e: 'projectDeleted', value: Project): void;\n (e: 'projectUpdated', value: Project): void;\n}>();\n\nconst userStore = useUserStore();\nconst projectSettingsModalStore = useProjectSettingsModalStore();\n\nconst isProjectOwner = computed(() => userStore.isProjectOwner(props.project));\n\nfunction openUserProfileInNewTab(href: string) {\n if (!href) return;\n window.open(href, '_blank');\n}\n</script>\n\n<template>\n <section\n class=\"relative rounded border border-new-gray-100/70 bg-white transition-shadow hover:border-new-gray-100 hover:shadow-lg dark:border-new-gray-950/80 dark:bg-gray-675\"\n >\n <div\n class=\"flex h-full flex-col gap-4 rounded-[3px] border-l-[5px] px-5 py-5\"\n :class=\"[project.type === 'app/invent' ? 'border-l-invent-blue' : 'border-l-new-gray-400']\"\n >\n <div class=\"flex flex-nowrap items-start gap-2\">\n <div class=\"flex w-full items-center gap-3 overflow-hidden\">\n <img\n class=\"h-9 w-9 flex-shrink-0 rounded bg-new-slate-300 object-cover dark:bg-new-gray-800\"\n :src=\"`${project.icon}?size=small`\"\n :alt=\"project.name\"\n width=\"36\"\n height=\"36\"\n loading=\"lazy\"\n />\n\n <div class=\"w-full overflow-hidden\">\n <div class=\"mb-1 flex gap-1.5\">\n <router-link\n :to=\"toProjectPage(project)\"\n class=\"truncate text-sm font-semibold leading-tight before:absolute before:inset-0 before:block\"\n >\n {{ project.name }}\n </router-link>\n\n <IconLock\n v-if=\"project.auth_required\"\n v-tooltip=\"{\n content: 'Private Project',\n }\"\n class=\"shrink-0 text-13 text-new-blue-400\"\n />\n </div>\n\n <p class=\"truncate text-xs leading-tight text-black/60 dark:text-white/60\">\n <span v-if=\"isProjectOwner\">\n {{ $t('general.last_edited') }}\n {{ formatTimeAgo(new Date(project.updated_at)) }}\n </span>\n\n <button\n v-else\n class=\"relative hover:underline\"\n @mouseup.middle=\"\n openUserProfileInNewTab(\n $router.resolve({\n name: 'profile',\n params: { usernameOrUserId: project.username || project.user_id },\n }).href,\n )\n \"\n @click=\"\n $router.push({\n name: 'profile',\n params: { usernameOrUserId: project.username || project.user_id },\n })\n \"\n >@{{ project.username || project.user_id }}</button\n >\n </p>\n </div>\n </div>\n\n <div class=\"-mr-2 -mt-1.5 flex items-center\">\n <FavoriteButton :project=\"project\" size=\"lg\" class=\"relative\" />\n\n <ProjectThreeDotMenu\n menu-btn-class-list=\"rounded-full text-new-gray-800 transition-colors hover:bg-new-gray-100/50 dark:text-new-gray-100 p-1\"\n :project=\"project\"\n :can-delete=\"isProjectOwner\"\n version=\"latest\"\n @project-deleted=\"$emit('projectDeleted', $event)\"\n @project-updated=\"$emit('projectUpdated', $event)\"\n />\n </div>\n </div>\n\n <div class=\"h-5 text-sm text-new-gray-700 dark:text-new-gray-200\">\n <button\n v-if=\"!project.description && isProjectOwner\"\n class=\"relative italic hover:underline\"\n @click=\"projectSettingsModalStore.open(props.project, 0)\"\n >\n Add a description...\n </button>\n <p v-else class=\"truncate\">{{ project.description }}</p>\n </div>\n\n <div class=\"flex items-center gap-6\">\n <ProjectCatalogTags\n class=\"pointer-events-none w-full\"\n :project=\"project\"\n :click-handler=\"tagClickHandler\"\n />\n\n <a\n v-tooltip=\"{ content: $t('buttons.view_site') }\"\n class=\"relative -m-1.5 rounded-full p-1.5 text-new-gray-800 transition-colors hover:bg-new-gray-100/50 dark:text-new-gray-100\"\n :href=\"project.latest.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :title=\"$t('buttons.view_site')\"\n >\n <IconExternalLink class=\"text-sm\" />\n </a>\n </div>\n </div>\n </section>\n</template>\n","<script lang=\"ts\" setup>\nimport { useUserStore } from '~/stores/user-store';\n\nconst userStore = useUserStore();\n</script>\n\n<template>\n <div class=\"flex items-center gap-4 px-4 py-2\">\n <div v-if=\"userStore.isLoggedIn\" class=\"w-1 min-w-0\">\n <slot name=\"favorite\" />\n </div>\n\n <div class=\"w-6\">\n <slot name=\"icon\" />\n </div>\n\n <div class=\"min-w-0 flex-[2]\">\n <slot name=\"title\" />\n </div>\n\n <div class=\"hidden min-w-0 flex-[5] lg:block\">\n <slot name=\"description\" />\n </div>\n\n <div class=\"hidden min-w-0 flex-[1] sm:block md:flex-[2]\">\n <slot name=\"tag\" />\n </div>\n\n <div class=\"hidden w-24 text-right sm:block\">\n <slot name=\"modified\" />\n </div>\n\n <div class=\"w-5 min-w-0\">\n <slot name=\"menu\" />\n </div>\n </div>\n</template>\n","<script lang=\"ts\" setup>\nimport { computed } from 'vue';\nimport { formatTimeAgo } from '@vueuse/core';\nimport type { Project } from '~/utilities/pyscript-api-models';\nimport { toProjectPage } from '~/utilities/to-project-page';\nimport { useUserStore } from '~/stores/user-store';\nimport { useProjectSettingsModalStore } from '~/stores/project-settings-modal-store';\nimport ProjectCatalogListColumns from '~/components/project-catalog/ProjectCatalogListColumns.vue';\nimport ProjectCatalogTags from '~/components/project-catalog/ProjectCatalogTags.vue';\nimport FavoriteButton from '~/components/buttons/FavoriteButton.vue';\nimport ProjectThreeDotMenu from '~/components/ProjectThreeDotMenu.vue';\nimport IconLock from '~icons/material-symbols/lock';\n\nconst props = defineProps<{\n project: Project;\n tagClickHandler: (...args: any) => void;\n lastItem: boolean;\n}>();\n\ndefineEmits<{\n (e: 'projectDeleted', value: Project): void;\n (e: 'projectUpdated', value: Project): void;\n}>();\n\nconst userStore = useUserStore();\nconst projectSettingsModalStore = useProjectSettingsModalStore();\n\nconst isProjectOwner = computed(() => userStore.isProjectOwner(props.project));\n\nconst lastEdited = computed(() => {\n return formatTimeAgo(new Date(props.project.updated_at));\n});\n\nfunction openUserProfileInNewTab(href: string) {\n if (!href) return;\n window.open(href, '_blank');\n}\n</script>\n\n<template>\n <ProjectCatalogListColumns\n class=\"group relative block px-2 text-13 hover:bg-new-gray-50 hover:dark:bg-new-gray-900\"\n :class=\"[lastItem ? 'rounded-b' : 'border-b border-new-gray-50 dark:border-new-gray-950/60']\"\n >\n <template #favorite>\n <FavoriteButton class=\"relative z-1 -translate-x-[12px]\" :project=\"project\" size=\"md\" />\n </template>\n\n <template #icon>\n <img\n class=\"mx-auto h-6 w-6 rounded bg-new-slate-300 object-cover dark:bg-new-gray-800\"\n :src=\"`${project.icon}?size=small`\"\n :alt=\"project.name\"\n width=\"24\"\n height=\"24\"\n loading=\"lazy\"\n />\n </template>\n\n <template #title>\n <div class=\"flex items-center gap-2\">\n <router-link\n :to=\"toProjectPage(project)\"\n :aria-label=\"project.name\"\n class=\"truncate font-medium leading-tight before:absolute before:inset-0 before:block\"\n >\n {{ project.name }}\n </router-link>\n\n <IconLock\n v-if=\"project.auth_required\"\n v-tooltip=\"{\n placement: 'bottom',\n content: 'Private Project',\n }\"\n class=\"shrink-0 text-xs text-new-blue-400\"\n />\n </div>\n\n <button\n v-if=\"!isProjectOwner\"\n class=\"truncate text-xs text-black/60 hover:underline dark:text-white/60\"\n @mouseup.middle=\"\n openUserProfileInNewTab(\n $router.resolve({\n name: 'profile',\n params: { usernameOrUserId: project.username || project.user_id },\n }).href,\n )\n \"\n @click=\"\n $router.push({\n name: 'profile',\n params: { usernameOrUserId: project.username || project.user_id },\n })\n \"\n >\n @{{ project.username || project.user_id }}\n </button>\n\n <div class=\"truncate text-xs text-black/60 dark:text-white/60 sm:hidden sm:text-13\">\n {{ lastEdited }}\n </div>\n </template>\n\n <template #description>\n <div class=\"text-black/60 dark:text-white/60\">\n <button\n v-if=\"!project.description && isProjectOwner\"\n class=\"relative italic hover:underline\"\n @click=\"projectSettingsModalStore.open(props.project, 0)\"\n >\n Add a description...\n </button>\n <p v-else class=\"truncate\">{{ project.description }}</p>\n </div>\n </template>\n\n <template #tag>\n <ProjectCatalogTags :project=\"project\" :click-handler=\"tagClickHandler\" />\n </template>\n\n <template #modified>\n <p class=\"truncate px-1 text-right\">\n <span class=\"leading-tight text-black/60 dark:text-white/60\">{{ lastEdited }}</span>\n </p>\n </template>\n\n <template #menu>\n <ProjectThreeDotMenu\n :project=\"project\"\n :can-delete=\"isProjectOwner\"\n version=\"latest\"\n menu-btn-class-list=\"rounded-full text-new-gray-800 transition-colors hover:bg-new-gray-100/50 dark:text-new-gray-100 p-1\"\n @project-deleted=\"$emit('projectDeleted', $event)\"\n @project-updated=\"$emit('projectUpdated', $event)\"\n />\n </template>\n </ProjectCatalogListColumns>\n</template>\n","<script setup lang=\"ts\">\nimport type { Project } from '~/utilities/pyscript-api-models';\nimport ProjectCatalogListItem from '~/components/project-catalog/ProjectCatalogListItem.vue';\nimport ProjectCatalogListColumns from '~/components/project-catalog/ProjectCatalogListColumns.vue';\nimport { useSortPreference } from '~/composables/sort-preference';\nimport IconChevronDown from '~icons/carbon/chevron-down';\n\ndefineProps<{\n projectList: Project[];\n animateLeave?: boolean;\n tagClickHandler: (...args: any) => void;\n}>();\n\ndefineEmits<{\n (e: 'projectDeleted', value: Project): void;\n (e: 'projectUpdated', value: Project): void;\n}>();\n\nconst { updateSort, savedSort } = useSortPreference();\n</script>\n\n<template>\n <section\n class=\"rounded border border-new-gray-100/70 bg-white dark:border-new-gray-950/60 dark:bg-gray-675\"\n >\n <div\n class=\"hidden border-b border-new-gray-50 py-0.5 text-sm font-medium dark:border-new-gray-950/60 sm:block\"\n >\n <ProjectCatalogListColumns>\n <template #title>\n <button\n class=\"flex w-full items-center gap-2 hover:underline\"\n @click=\"\n updateSort({\n sortBy: 'name',\n sortOrder: savedSort.sortOrder === 'desc' ? 'asc' : 'desc',\n })\n \"\n >\n <span>Title</span>\n <IconChevronDown\n class=\"shrink-0 text-13\"\n :class=\"[\n savedSort.sortBy === 'name' && savedSort.sortOrder === 'asc' ? 'rotate-180' : '',\n savedSort.sortBy === 'name' ? '' : 'opacity-0',\n ]\"\n />\n </button>\n </template>\n\n <template #description>\n <span class=\"cursor-default\">Description</span>\n </template>\n\n <template #tag>\n <span class=\"cursor-default\">Tag</span>\n </template>\n\n <template #modified>\n <button\n class=\"flex w-full items-center justify-end gap-2 hover:underline\"\n @click=\"\n updateSort({\n sortBy: 'updated_at',\n sortOrder: savedSort.sortOrder === 'desc' ? 'asc' : 'desc',\n })\n \"\n >\n <span class=\"whitespace-nowrap\">Last Modified</span>\n <IconChevronDown\n class=\"-mr-5 shrink-0 text-13\"\n :class=\"[\n savedSort.sortBy === 'updated_at' && savedSort.sortOrder === 'asc'\n ? 'rotate-180'\n : '',\n savedSort.sortBy === 'updated_at' ? '' : 'opacity-0',\n ]\"\n />\n </button>\n </template>\n </ProjectCatalogListColumns>\n </div>\n\n <TransitionGroup\n tag=\"section\"\n :css=\"Boolean(animateLeave)\"\n name=\"project-list-slide\"\n class=\"relative\"\n >\n <ProjectCatalogListItem\n v-for=\"(proj, index) of projectList\"\n :key=\"proj.id\"\n :project=\"proj\"\n :tag-click-handler=\"tagClickHandler\"\n :last-item=\"index === projectList.length - 1\"\n @project-deleted=\"$emit('projectDeleted', $event)\"\n @project-updated=\"$emit('projectUpdated', $event)\"\n />\n </TransitionGroup>\n </section>\n</template>\n\n<style lang=\"postcss\" scoped>\n/* Only add animations if the user doesn't prefer reduced motion. */\n@media (prefers-reduced-motion: no-preference) {\n .project-list-slide {\n &-move,\n &-enter-active,\n &-leave-active {\n transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);\n }\n\n &-enter-from,\n &-leave-to {\n opacity: 0;\n transform: scaleY(0.01) translate(30px, 0);\n }\n\n &-leave-active {\n position: absolute;\n }\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport ArrowLeft from '~icons/carbon/chevron-left';\nimport ArrowRight from '~icons/carbon/chevron-right';\n\nconst props = defineProps<{\n currentPage: number;\n totalPages: number;\n totalCount: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n setPage: (pageNumber: number) => void;\n}>();\n\nconst reversedPageNumbers = computed(() => {\n const maxPages = Math.min(props.currentPage - 1, 2);\n return Array.from({ length: maxPages }, (_, index) => maxPages - index);\n});\n\nfunction nextPage() {\n props.hasNextPage && props.setPage(props.currentPage + 1);\n}\n\nfunction previousPage() {\n props.hasPreviousPage && props.setPage(props.currentPage - 1);\n}\n</script>\n\n<template>\n <nav class=\"z-0 flex -space-x-px shadow-sm\">\n <!-- Previous Page Button -->\n <button :disabled=\"!hasPreviousPage\" class=\"page-btn rounded-l-md\" @click=\"previousPage()\">\n <ArrowLeft class=\"-mx-1\" />\n </button>\n\n <!-- First Page Button -->\n <button v-if=\"currentPage > 3\" class=\"page-btn\" @click=\"setPage(1)\">1</button>\n\n <!-- Dots for indicating previous pages -->\n <span v-if=\"currentPage > 4\" class=\"page-btn --ellipsis\">...</span>\n\n <!-- Previous 3 Pages -->\n <button\n v-for=\"pageNumber in reversedPageNumbers\"\n :key=\"pageNumber\"\n class=\"page-btn\"\n @click=\"setPage(currentPage - pageNumber)\"\n >\n {{ currentPage - pageNumber }}\n </button>\n\n <!-- Current Page -->\n <button class=\"page-btn --selected\">{{ currentPage }}</button>\n\n <!-- Next 3 Pages -->\n <button\n v-for=\"pageNumber in Math.min(totalPages - currentPage, 2)\"\n :key=\"pageNumber\"\n class=\"page-btn\"\n @click=\"setPage(currentPage + pageNumber)\"\n >\n {{ currentPage + pageNumber }}\n </button>\n\n <!-- Dots for indicating next pages -->\n <span v-if=\"currentPage < totalPages - 3\" class=\"page-btn --ellipsis\">...</span>\n\n <!-- Last Page Button -->\n <button v-if=\"currentPage < totalPages - 2\" class=\"page-btn\" @click=\"setPage(totalPages)\">\n {{ totalPages }}\n </button>\n\n <!-- Next Page Button -->\n <button :disabled=\"!hasNextPage\" class=\"page-btn rounded-r-md\" @click=\"nextPage()\">\n <ArrowRight class=\"-mx-1\" />\n </button>\n </nav>\n</template>\n\n<style scoped lang=\"postcss\">\n.page-btn {\n @apply relative inline-flex items-center border bg-white px-3.5 py-2 text-xs hover:bg-new-gray-50 dark:bg-gray-675;\n @apply outline-none focus-visible:outline-offset-0 focus-visible:ring-1 focus-visible:ring-inset;\n @apply disabled:bg-new-gray-50 disabled:text-new-gray-600 dark:border-new-gray-950/80 disabled:dark:bg-new-gray-900;\n\n &.--selected {\n @apply cursor-default bg-new-green-300 dark:bg-new-green-700 dark:text-new-content-900;\n }\n\n &.--number {\n @apply px-3.5;\n }\n\n &.--ellipsis {\n @apply cursor-default hover:bg-white;\n }\n}\n</style>\n","import { useStorage } from '@vueuse/core';\nimport { STORAGE_KEY_PROJECTS_VIEW } from '~/utilities/constants';\n\nexport type ListOrGrid = 'list' | 'grid';\n\nexport function useListGridViewPreference() {\n const savedView = useStorage<ListOrGrid>(STORAGE_KEY_PROJECTS_VIEW, 'grid');\n const toggleView = () => (savedView.value = savedView.value === 'list' ? 'grid' : 'list');\n\n return {\n savedView,\n toggleView,\n };\n}\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue';\nimport ProjectCatalogGrid from '~/components/project-catalog/ProjectCatalogGrid.vue';\nimport ProjectCatalogList from '~/components/project-catalog/ProjectCatalogList.vue';\nimport Pagination from '~/components/Pagination.vue';\nimport type { Project } from '~/utilities/pyscript-api-models';\nimport { useListGridViewPreference } from '~/composables/list-grid-view-preference';\nimport type { ProjectPagination } from '~/composables/paginated-project-list';\n\nconst props = defineProps<{\n projectList: Project[];\n pagination?: ProjectPagination;\n setPage?: (value: number) => void;\n tagClickHandler: (...args: any) => void;\n /** Used to refetch the project list when a project is deleted or updated. */\n refetch: () => Promise<void>;\n}>();\n\nconst emit = defineEmits<{\n (e: 'projectDeleted', value: Project): void;\n (e: 'projectUpdated', value: Project): void;\n}>();\n\nconst { savedView } = useListGridViewPreference();\n\nconst projectWasDeleted = ref(false);\n\nasync function onProjectDeleted(value: Project) {\n projectWasDeleted.value = true;\n await props.refetch();\n // Gives time for the leave animation to finish.\n setTimeout(() => (projectWasDeleted.value = false), 500);\n emit('projectDeleted', value);\n}\n\nasync function onProjectUpdated(value: Project) {\n await props.refetch();\n emit('projectUpdated', value);\n}\n\nconst itemsRange = computed(() => {\n if (!props.pagination) return;\n\n const { page, page_size, total_count, total_pages } = props.pagination;\n return {\n start: page * page_size - (page_size - 1),\n end: page === total_pages ? total_count : page * page_size,\n total: total_count,\n };\n});\n</script>\n\n<template>\n <section>\n <ProjectCatalogGrid\n v-if=\"savedView === 'grid'\"\n :project-list=\"projectList\"\n :animate-leave=\"projectWasDeleted\"\n :tag-click-handler=\"tagClickHandler\"\n @project-deleted=\"onProjectDeleted\"\n @project-updated=\"onProjectUpdated\"\n />\n\n <ProjectCatalogList\n v-else\n :project-list=\"projectList\"\n :animate-leave=\"projectWasDeleted\"\n :tag-click-handler=\"tagClickHandler\"\n @project-deleted=\"onProjectDeleted\"\n @project-updated=\"onProjectUpdated\"\n />\n\n <div\n v-if=\"pagination && itemsRange && setPage && itemsRange.end > 0\"\n class=\"mt-5 flex flex-col items-center gap-3\"\n >\n <div class=\"text-center text-xs\">\n Showing <span class=\"font-medium\">{{ itemsRange.start }}</span> to\n <span class=\"font-medium\">{{ itemsRange.end }}</span> of\n <span class=\"font-medium\">{{ itemsRange.total }}</span> results\n </div>\n\n <Pagination\n :current-page=\"pagination.page\"\n :total-pages=\"pagination.total_pages\"\n :total-count=\"pagination.total_count\"\n :has-next-page=\"Boolean(pagination.next_page)\"\n :has-previous-page=\"Boolean(pagination.previous_page)\"\n :set-page=\"setPage\"\n />\n </div>\n </section>\n</template>\n","<script setup lang=\"ts\">\nimport { nextTick, ref } from 'vue';\nimport { useI18n } from 'vue-i18n';\nimport IconSearch from '~icons/carbon/search';\nimport IconClose from '~icons/carbon/close';\n\nconst props = defineProps<{\n searchFn?: () => void;\n modelValue: string;\n isOpen: boolean;\n placeholder?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', modelValue: string): void;\n (e: 'update:isOpen', isOpen: boolean): void;\n}>();\n\nconst { t } = useI18n();\nconst searchInput = ref<HTMLInputElement>();\n\nfunction onInput(event: Event) {\n if (event.target instanceof HTMLInputElement) {\n emit('update:modelValue', event.target.value);\n props.searchFn && props.searchFn();\n }\n}\n\nfunction onToggleSearch(status: boolean) {\n emit('update:isOpen', status);\n\n if (status) {\n nextTick(() => {\n searchInput.value?.focus();\n });\n } else {\n // Reset the search query when the search input is closed.\n emit('update:modelValue', '');\n props.searchFn && props.searchFn();\n }\n}\n</script>\n\n<template>\n <div class=\"flex\" :class=\"[isOpen ? 'absolute inset-0' : '']\">\n <div class=\"relative w-full\" :class=\"[isOpen ? '' : 'hidden']\">\n <IconSearch\n class=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-new-gray-900 dark:text-white\"\n aria-hidden=\"true\"\n />\n <input\n id=\"posts-search\"\n ref=\"searchInput\"\n :value=\"modelValue\"\n type=\"search\"\n :placeholder=\"placeholder || `${t('general.search_placeholder')}...`\"\n autocomplete=\"off\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n aria-label=\"Search your projects\"\n class=\"h-full w-full rounded-full border border-new-gray-100 bg-white py-1.5 pl-9 pr-3 transition-colors placeholder:text-sm focus:bg-white focus:outline-none dark:border-new-gray-900 dark:bg-gray-650 focus:dark:bg-gray-700\"\n @input=\"onInput\"\n />\n </div>\n\n <button class=\"flex w-8 flex-none items-center justify-center\" @click=\"onToggleSearch(!isOpen)\">\n <IconClose\n v-if=\"isOpen\"\n class=\"text-xl text-new-gray-900 dark:text-white/50\"\n aria-hidden=\"true\"\n />\n <IconSearch\n v-else\n class=\"text-base text-new-gray-900 dark:text-white/50\"\n aria-hidden=\"true\"\n />\n </button>\n </div>\n</template>\n\n<style scoped lang=\"postcss\"></style>\n","<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n';\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';\nimport { computed } from 'vue';\nimport { useSortPreference } from '~/composables/sort-preference';\nimport IconClockDesc from '~icons/mdi/sort-clock-descending-outline';\nimport IconCheck from '~icons/ph/check-bold';\nimport IconCalendar from '~icons/ph/calendar-blank';\nimport IconTime from '~icons/carbon/time';\nimport IconAlpha from '~icons/bi/alphabet-uppercase';\nimport IconArrowUp from '~icons/heroicons/arrow-long-up';\n\nconst { t } = useI18n();\nconst { savedSort, updateSort, sortOptions } = useSortPreference();\n\nconst IconSort = computed(() => {\n const { sortBy } = savedSort.value;\n\n if (sortBy === 'name') {\n return IconAlpha;\n }\n\n if (sortBy === 'created_at') {\n return IconCalendar;\n }\n\n if (sortBy === 'updated_at') {\n return IconTime;\n }\n\n return IconClockDesc;\n});\n</script>\n\n<template>\n <Menu as=\"div\" class=\"three-dot-menu\">\n <MenuButton aria-label=\"Sort options\" class=\"three-dot-menu__btn icon-block h-8\">\n <div\n v-tooltip=\"{\n content: t('tooltips.sort_options'),\n }\"\n class=\"flex items-center justify-center\"\n >\n <component :is=\"IconSort\" aria-hidden=\"true\" class=\"text-sm\" />\n <IconArrowUp\n class=\"-ml-0.5 -mr-1 text-sm\"\n :class=\"[savedSort.sortOrder === 'desc' ? 'rotate-180' : '']\"\n aria-hidden=\"true\"\n />\n </div>\n </MenuButton>\n\n <transition name=\"animate-menu-panel\">\n <MenuItems class=\"three-dot-menu__panel --right min-w-[8rem]\">\n <template v-for=\"item in sortOptions\" :key=\"item\">\n <hr v-if=\"item.id === 'spacer'\" class=\"border-t-3 mx-1 my-1 dark:border-t-new-gray-400\" />\n\n <MenuItem v-else-if=\"item.value\">\n <button\n class=\"three-dot-menu__item-btn gap-1 pl-1 pr-2.5\"\n @click=\"updateSort(item.value)\"\n >\n <IconCheck\n class=\"text-xs\"\n :aria-hidden=\"item.value === savedSort\"\n :class=\"[\n item.value.sortBy === savedSort.sortBy &&\n item.value.sortOrder === savedSort.sortOrder\n ? ''\n : 'opacity-0',\n ]\"\n />\n <span\n class=\"three-dot-menu__item-text\"\n :data-content=\"item.text\"\n v-text=\"item.text\"\n />\n </button>\n </MenuItem>\n </template>\n </MenuItems>\n </transition>\n </Menu>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n';\nimport { useListGridViewPreference } from '~/composables/list-grid-view-preference';\nimport IconGrid from '~icons/ph/grid-four-light';\nimport IconList from '~icons/ph/list-dashes';\n\nconst { t } = useI18n();\nconst { savedView, toggleView } = useListGridViewPreference();\n</script>\n\n<template>\n <button\n v-tooltip=\"{\n content: savedView === 'list' ? t('tooltips.grid_view') : t('tooltips.list_view'),\n }\"\n class=\"icon-block flex h-8 cursor-pointer items-center justify-center\"\n @click=\"toggleView\"\n >\n <span class=\"flex w-5 items-center justify-center\">\n <IconGrid\n v-if=\"savedView === 'list'\"\n class=\"shrink-0 text-[18px] text-new-gray-800 dark:text-new-gray-100\"\n />\n <IconList v-else class=\"shrink-0 text-[18px] text-new-gray-800 dark:text-new-gray-100\" />\n </span>\n </button>\n</template>\n","import type { MaybeRef, Ref } from 'vue';\nimport { ref, toValue, watch, watchEffect } from 'vue';\nimport { refDebounced, useStorage } from '@vueuse/core';\nimport { STORAGE_KEY_ITEMS_PER_PAGE } from '~/utilities/constants';\nimport pyscriptApi from '~/utilities/pyscript-api';\nimport type {\n ListProjectsOptions,\n ListProjectsResponse,\n Project,\n} from '~/utilities/pyscript-api-models';\nimport { useSortPreference } from '~/composables/sort-preference';\n\nconst { savedSort } = useSortPreference();\n\nexport type ProjectPagination = Omit<ListProjectsResponse, 'results'>;\n\nexport interface UsePaginatedProjectListOptions {\n user?: MaybeRef<ListProjectsOptions['user']>;\n /** Only used if fetching projects for a collection. */\n collectionId?: string;\n q?: Ref<ListProjectsOptions['q']>;\n scope?: MaybeRef<ListProjectsOptions['scope']>;\n}\n\nexport interface UsePaginatedProjectListReturn {\n projectList: Ref<Project[] | null>;\n pagination: Ref<ProjectPagination | null>;\n setPage: (value: number) => void;\n refetch: () => Promise<void>;\n hasNoProjects: Ref<boolean>;\n}\n\nexport type ItemsPerPageOptions = 10 | 20 | 50 | 100;\n\n/** Local storage saved value for number of items to show per page. */\nexport const savedItemsPerPage = useStorage<ItemsPerPageOptions>(STORAGE_KEY_ITEMS_PER_PAGE, 100);\n\nexport function usePaginatedProjectList(\n options: UsePaginatedProjectListOptions,\n): UsePaginatedProjectListReturn {\n const controller = ref<AbortController>();\n const query = refDebounced(ref(options.q), 300);\n\n // State\n const page = ref(1);\n const projectList = ref<Project[] | null>(null);\n const pagination = ref<ProjectPagination | null>(null);\n const hasNoProjects = ref<boolean>(false);\n\n // Reset the state when the `options.user` changes.\n watch(\n () => options.user,\n () => {\n page.value = 1;\n projectList.value = null;\n pagination.value = null;\n },\n { deep: true },\n );\n\n // Reset the page number when the page size, query, or sort changes.\n watch([savedItemsPerPage, query, savedSort], () => (page.value = 1));\n\n watchEffect(async () => {\n // Cancel the previous request.\n if (controller.value) {\n controller.value.abort();\n }\n\n controller.value = new AbortController();\n const signal = controller.value.signal;\n\n try {\n await listProjects(signal);\n } catch (error) {}\n });\n\n async function listProjects(signal?: AbortSignal) {\n let resp: ListProjectsResponse;\n\n if (options.collectionId) {\n resp = await pyscriptApi.listProjectsInCollection(\n options.collectionId,\n {\n scope: toValue(options.scope),\n user: toValue(options.user),\n q: toValue(query),\n pageSize: savedItemsPerPage.value,\n page: page.value,\n sortBy: savedSort.value.sortBy,\n sortOrder: savedSort.value.sortOrder,\n },\n signal,\n );\n } else {\n resp = await pyscriptApi.listProjects(\n {\n scope: toValue(options.scope),\n user: toValue(options.user),\n q: toValue(query),\n pageSize: savedItemsPerPage.value,\n page: page.value,\n sortBy: savedSort.value.sortBy,\n sortOrder: savedSort.value.sortOrder,\n },\n signal,\n );\n }\n\n if (resp) {\n const { results, ...paginationData } = resp;\n\n projectList.value = results;\n pagination.value = paginationData;\n hasNoProjects.value = !query.value && results.length === 0;\n }\n }\n\n function setPage(value: number) {\n page.value = value;\n }\n\n async function refetch() {\n await listProjects();\n }\n\n return {\n projectList,\n pagination,\n setPage,\n refetch,\n hasNoProjects,\n };\n}\n","<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';\nimport type { ItemsPerPageOptions } from '~/composables/paginated-project-list';\nimport { savedItemsPerPage } from '~/composables/paginated-project-list';\nimport CaretDown from '~icons/carbon/caret-down';\nimport IconCheck from '~icons/ph/check-bold';\n\nconst options: ItemsPerPageOptions[] = [10, 20, 50, 100];\n\nasync function selectItemsPerPage(option: ItemsPerPageOptions) {\n savedItemsPerPage.value = option;\n}\n</script>\n\n<template>\n <div class=\"flex items-center gap-2\">\n <Menu as=\"div\" class=\"three-dot-menu\">\n <MenuButton as=\"button\" class=\"menu-btn-trigger\">\n {{ savedItemsPerPage }} <CaretDown class=\"ml-0.5\" />\n </MenuButton>\n\n <Transition name=\"animate-menu-panel\">\n <MenuItems class=\"three-dot-menu__panel --right mt-1 min-w-[5rem] px-0.5\">\n <MenuItem\n v-for=\"option in options\"\n :key=\"option\"\n as=\"button\"\n class=\"three-dot-menu__item-btn gap-1 text-sm\"\n @click=\"selectItemsPerPage(option)\"\n >\n <IconCheck\n class=\"text-xs\"\n :aria-hidden=\"option === savedItemsPerPage\"\n :class=\"[option === savedItemsPerPage ? '' : 'opacity-0']\"\n />\n\n {{ option }}\n </MenuItem>\n </MenuItems>\n </Transition>\n </Menu>\n\n <span class=\"text-xs max-sm:hidden\">Items per page</span>\n </div>\n</template>\n\n<style scoped lang=\"postcss\">\n.menu-btn-trigger {\n @apply relative inline-flex items-center rounded border bg-white px-2 py-1 text-xs hover:bg-new-gray-50 dark:bg-gray-675;\n @apply outline-none focus-visible:outline-offset-0 focus-visible:ring-1 focus-visible:ring-inset;\n @apply dark:border-new-gray-950/80;\n\n &.--selected {\n @apply cursor-default bg-new-green-300 dark:bg-new-green-700 dark:text-new-content-900;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { reactive } from 'vue';\nimport { useRouter } from 'vue-router';\nimport Spinner from '~/components/Spinner.vue';\nimport { createNewProject } from '~/utilities/create-new-project';\nimport { toProjectPage } from '~/utilities/to-project-page';\nimport IconAdd from '~icons/carbon/add';\n\nconst router = useRouter();\n\nconst state = reactive({\n isCreatingNewProject: false,\n});\n\nasync function navigateToNewProjectPage() {\n state.isCreatingNewProject = true;\n const newProject = await createNewProject();\n await router.push(toProjectPage(newProject));\n state.isCreatingNewProject = false;\n}\n</script>\n\n<template>\n <button\n class=\"btn --primary --space-sm inline-flex items-center gap-2 text-sm disabled:cursor-not-allowed disabled:opacity-50\"\n :disabled=\"state.isCreatingNewProject\"\n @click=\"navigateToNewProjectPage\"\n >\n <Spinner v-if=\"state.isCreatingNewProject\" class=\"h-4 w-4 flex-none\" />\n <IconAdd v-else class=\"-mx-1.5 text-base\" />\n\n <span>\n <template v-if=\"state.isCreatingNewProject\">{{ $t('buttons.creating_project') }}...</template>\n <template v-else>\n {{ $t('states.no_items_created.button_text', { noun: $t('states.noun.projects', 1) }) }}\n </template>\n </span>\n </button>\n</template>\n\n<style scoped lang=\"postcss\"></style>\n","<script setup lang=\"ts\">\nimport { useHead } from '@unhead/vue';\nimport { computed, reactive, ref } from 'vue';\nimport { useProjectSettingsModalStore } from '~/stores/project-settings-modal-store';\nimport AppHeader from '~/components/AppHeader.vue';\nimport SearchProjects from '~/components/SearchProjects.vue';\nimport SearchProjectsCollapsible from '~/components/SearchProjectsCollapsible.vue';\nimport ProjectCatalog from '~/components/project-catalog/ProjectCatalog.vue';\nimport ProjectSettingsModal from '~/components/project-settings/ProjectSettingsModal.vue';\nimport ListGridToggle from '~/components/ListGridToggle.vue';\nimport SortDropdown from '~/components/SortDropdown.vue';\nimport EmptySearchResults from '~/components/EmptySearchResults.vue';\nimport ItemsPerPageDropdown from '~/components/ItemsPerPageDropdown.vue';\nimport EmptyNoneCreated from '~/components/EmptyNoneCreated.vue';\nimport { usePaginatedProjectList } from '~/composables/paginated-project-list';\n\nuseHead({ title: 'Favorites' });\n\nconst projectSettingsModalStore = useProjectSettingsModalStore();\n\nconst state = reactive({\n isSearchOpen: false,\n});\n\nconst searchQuery = ref('');\n\nconst projectPagination = reactive(\n usePaginatedProjectList({\n scope: 'favorites',\n q: searchQuery,\n }),\n);\n\nconst isLoading = computed(() => projectPagination.projectList === null);\n\nasync function fetchFavorites() {\n await projectPagination.refetch();\n}\n\nfunction onTagClick(tag: string) {\n state.isSearchOpen = true;\n searchQuery.value = searchQuery.value === tag ? '' : tag;\n}\n</script>\n\n<template>\n <div\n class=\"container-spacing border-b border-new-gray-100/80 bg-white dark:border-new-gray-950/80 dark:bg-new-slate-700\"\n >\n <AppHeader class=\"flex items-center !gap-0\">\n <template #leftColumn>\n <div class=\"relative flex w-full items-center\">\n <h1 class=\"text-lg font-semibold\">{{ $t('favorites.page_heading') }}</h1>\n\n <!-- Visible on mobile only. -->\n <div class=\"ml-auto flex h-8 items-center md:hidden\">\n <SearchProjectsCollapsible v-model=\"searchQuery\" v-model:is-open=\"state.isSearchOpen\" />\n </div>\n </div>\n </template>\n\n <template #centerColumn>\n <!-- Visible on desktop only. -->\n <SearchProjects v-model=\"searchQuery\" class=\"hidden md:block md:w-80\" />\n </template>\n </AppHeader>\n </div>\n\n <div class=\"container-spacing flex-auto overflow-auto pb-4 pt-3\">\n <div class=\"mb-3 flex items-center\">\n <div class=\"ml-auto flex\">\n <ListGridToggle />\n <SortDropdown />\n <ItemsPerPageDropdown class=\"ml-1.5\" />\n </div>\n </div>\n\n <template v-if=\"!isLoading\">\n <!-- No Favorites -->\n <EmptyNoneCreated\n v-if=\"projectPagination.hasNoProjects\"\n class=\"mt-10\"\n i18n-key=\"states.noun.favorites\"\n :description=\"$t('favorites.no_favorites.description')\"\n />\n\n <!-- No search results -->\n <EmptySearchResults\n v-else-if=\"searchQuery && !projectPagination.projectList?.length\"\n i18n-key=\"states.noun.favorites\"\n />\n\n <!-- Users's projects -->\n <ProjectCatalog\n v-else-if=\"projectPagination.projectList && projectPagination.pagination\"\n :project-list=\"projectPagination.projectList\"\n :pagination=\"projectPagination.pagination\"\n :set-page=\"projectPagination.setPage\"\n :tag-click-handler=\"onTagClick\"\n :refetch=\"fetchFavorites\"\n />\n </template>\n </div>\n\n <ProjectSettingsModal\n v-if=\"projectSettingsModalStore.project\"\n @project-deleted=\"\n () => {\n projectSettingsModalStore.close();\n fetchFavorites();\n }\n \"\n @project-updated=\"fetchFavorites\"\n @project-auth-updated=\"projectSettingsModalStore.$patch({ project: $event })\"\n />\n</template>\n\n<style scoped lang=\"postcss\"></style>\n"],"names":["props","__props","t","useI18n","userStore","useUserStore","projectSettingsModalStore","useProjectSettingsModalStore","isProjectOwner","computed","openUserProfileInNewTab","href","lastEdited","formatTimeAgo","updateSort","savedSort","useSortPreference","reversedPageNumbers","maxPages","_","index","nextPage","previousPage","useListGridViewPreference","savedView","useStorage","STORAGE_KEY_PROJECTS_VIEW","emit","__emit","projectWasDeleted","ref","onProjectDeleted","value","onProjectUpdated","itemsRange","page","page_size","total_count","total_pages","searchInput","onInput","event","onToggleSearch","status","nextTick","_a","sortOptions","IconSort","sortBy","IconAlpha","IconCalendar","IconTime","IconClockDesc","toggleView","savedItemsPerPage","STORAGE_KEY_ITEMS_PER_PAGE","usePaginatedProjectList","options","controller","query","refDebounced","projectList","pagination","hasNoProjects","watch","watchEffect","signal","listProjects","resp","pyscriptApi","toValue","results","paginationData","setPage","refetch","selectItemsPerPage","option","router","useRouter","state","reactive","navigateToNewProjectPage","newProject","createNewProject","toProjectPage","useHead","searchQuery","projectPagination","isLoading","fetchFavorites","onTagClick","tag"],"mappings":"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"}