{"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":"kqCAOA,MAAMA,EAAQC,EAKR,CAAE,EAAAC,GAAMC,IACRC,EAAYC,IACZC,EAA4BC,IAC5BC,EAAiBC,EAAS,IAAML,EAAU,eAAeJ,EAAM,OAAO,CAAC,g/CCF7E,MAAMA,EAAQC,EAURG,EAAYC,IACZC,EAA4BC,IAE5BC,EAAiBC,EAAS,IAAML,EAAU,eAAeJ,EAAM,OAAO,CAAC,EAE7E,SAASU,EAAwBC,EAAc,CACxCA,GACE,OAAA,KAAKA,EAAM,QAAQ,CAC5B,26GC5BA,MAAMP,EAAYC,uxBCUlB,MAAML,EAAQC,EAWRG,EAAYC,IACZC,EAA4BC,IAE5BC,EAAiBC,EAAS,IAAML,EAAU,eAAeJ,EAAM,OAAO,CAAC,EAEvEY,EAAaH,EAAS,IACnBI,EAAc,IAAI,KAAKb,EAAM,QAAQ,UAAU,CAAC,CACxD,EAED,SAASU,EAAwBC,EAAc,CACxCA,GACE,OAAA,KAAKA,EAAM,QAAQ,CAC5B,yzFClBA,KAAM,CAAE,WAAAG,EAAY,UAAAC,CAAU,EAAIC,EAAkB,2pECbpD,MAAMhB,EAAQC,EASRgB,EAAsBR,EAAS,IAAM,CACzC,MAAMS,EAAW,KAAK,IAAIlB,EAAM,YAAc,EAAG,CAAC,EAC3C,OAAA,MAAM,KAAK,CAAE,OAAQkB,CAAA,EAAY,CAACC,EAAGC,IAAUF,EAAWE,CAAK,CAAA,CACvE,EAED,SAASC,GAAW,CAClBrB,EAAM,aAAeA,EAAM,QAAQA,EAAM,YAAc,CAAC,CAC1D,CAEA,SAASsB,GAAe,CACtBtB,EAAM,iBAAmBA,EAAM,QAAQA,EAAM,YAAc,CAAC,CAC9D,giCCpBO,SAASuB,IAA4B,CACpC,MAAAC,EAAYC,EAAuBC,GAA2B,MAAM,EAGnE,MAAA,CACL,UAAAF,EACA,WAJiB,IAAOA,EAAU,MAAQA,EAAU,QAAU,OAAS,OAAS,MAIhF,CAEJ,2XCJA,MAAMxB,EAAQC,EASR0B,EAAOC,EAKP,CAAE,UAAAJ,GAAcD,KAEhBM,EAAoBC,EAAI,EAAK,EAEnC,eAAeC,EAAiBC,EAAgB,CAC9CH,EAAkB,MAAQ,GAC1B,MAAM7B,EAAM,UAEZ,WAAW,IAAO6B,EAAkB,MAAQ,GAAQ,GAAG,EACvDF,EAAK,iBAAkBK,CAAK,CAC9B,CAEA,eAAeC,EAAiBD,EAAgB,CAC9C,MAAMhC,EAAM,UACZ2B,EAAK,iBAAkBK,CAAK,CAC9B,CAEM,MAAAE,EAAazB,EAAS,IAAM,CAChC,GAAI,CAACT,EAAM,WAAY,OAEvB,KAAM,CAAE,KAAAmC,EAAM,UAAAC,EAAW,YAAAC,EAAa,YAAAC,GAAgBtC,EAAM,WACrD,MAAA,CACL,MAAOmC,EAAOC,GAAaA,EAAY,GACvC,IAAKD,IAASG,EAAcD,EAAcF,EAAOC,EACjD,MAAOC,CAAA,CACT,CACD,suCC3CD,MAAMrC,EAAQC,EAOR0B,EAAOC,EAKP,CAAE,EAAA1B,GAAMC,IACRoC,EAAcT,IAEpB,SAASU,EAAQC,EAAc,CACzBA,EAAM,kBAAkB,mBACrBd,EAAA,oBAAqBc,EAAM,OAAO,KAAK,EACtCzC,EAAA,UAAYA,EAAM,WAE5B,CAEA,SAAS0C,EAAeC,EAAiB,CACvChB,EAAK,gBAAiBgB,CAAM,EAExBA,EACFC,GAAS,IAAM,QACbC,EAAAN,EAAY,QAAZ,MAAAM,EAAmB,OAAM,CAC1B,GAGDlB,EAAK,oBAAqB,EAAE,EACtB3B,EAAA,UAAYA,EAAM,WAE5B,2uHC5BM,KAAA,CAAE,EAAAE,GAAMC,IACR,CAAE,UAAAY,EAAW,WAAAD,EAAY,YAAAgC,GAAgB9B,EAAkB,EAE3D+B,EAAWtC,EAAS,IAAM,CACxB,KAAA,CAAE,OAAAuC,CAAO,EAAIjC,EAAU,MAE7B,OAAIiC,IAAW,OACNC,GAGLD,IAAW,aACNE,GAGLF,IAAW,aACNG,GAGFC,EAAA,CACR,2lECzBK,KAAA,CAAE,EAAAlD,GAAMC,IACR,CAAE,UAAAqB,EAAW,WAAA6B,CAAW,EAAI9B,GAA0B,yeCKtD,CAAE,UAAAR,CAAU,EAAIC,IAuBTsC,EAAoB7B,EAAgC8B,GAA4B,GAAG,EAEzF,SAASC,GACdC,EAC+B,CAC/B,MAAMC,EAAa5B,IACb6B,EAAQC,GAAa9B,EAAI2B,EAAQ,CAAC,EAAG,GAAG,EAGxCtB,EAAOL,EAAI,CAAC,EACZ+B,EAAc/B,EAAsB,IAAI,EACxCgC,EAAahC,EAA8B,IAAI,EAC/CiC,EAAgBjC,EAAa,EAAK,EAGxCkC,EACE,IAAMP,EAAQ,KACd,IAAM,CACJtB,EAAK,MAAQ,EACb0B,EAAY,MAAQ,KACpBC,EAAW,MAAQ,IACrB,EACA,CAAE,KAAM,EAAK,CAAA,EAITE,EAAA,CAACV,EAAmBK,EAAO5C,CAAS,EAAG,IAAOoB,EAAK,MAAQ,CAAE,EAEnE8B,GAAY,SAAY,CAElBP,EAAW,OACbA,EAAW,MAAM,QAGRA,EAAA,MAAQ,IAAI,gBACjB,MAAAQ,EAASR,EAAW,MAAM,OAE5B,GAAA,CACF,MAAMS,EAAaD,CAAM,OACX,CAAC,CAAA,CAClB,EAED,eAAeC,EAAaD,EAAsB,CAC5C,IAAAE,EA+BJ,GA7BIX,EAAQ,aACVW,EAAO,MAAMC,EAAY,yBACvBZ,EAAQ,aACR,CACE,MAAOa,EAAQb,EAAQ,KAAK,EAC5B,KAAMa,EAAQb,EAAQ,IAAI,EAC1B,EAAGa,EAAQX,CAAK,EAChB,SAAUL,EAAkB,MAC5B,KAAMnB,EAAK,MACX,OAAQpB,EAAU,MAAM,OACxB,UAAWA,EAAU,MAAM,SAC7B,EACAmD,CAAA,EAGFE,EAAO,MAAMC,EAAY,aACvB,CACE,MAAOC,EAAQb,EAAQ,KAAK,EAC5B,KAAMa,EAAQb,EAAQ,IAAI,EAC1B,EAAGa,EAAQX,CAAK,EAChB,SAAUL,EAAkB,MAC5B,KAAMnB,EAAK,MACX,OAAQpB,EAAU,MAAM,OACxB,UAAWA,EAAU,MAAM,SAC7B,EACAmD,CAAA,EAIAE,EAAM,CACR,KAAM,CAAE,QAAAG,EAAS,GAAGC,EAAA,EAAmBJ,EAEvCP,EAAY,MAAQU,EACpBT,EAAW,MAAQU,GACnBT,EAAc,MAAQ,CAACJ,EAAM,OAASY,EAAQ,SAAW,CAC3D,CACF,CAEA,SAASE,EAAQzC,EAAe,CAC9BG,EAAK,MAAQH,CACf,CAEA,eAAe0C,GAAU,CACvB,MAAMP,EAAa,CACrB,CAEO,MAAA,CACL,YAAAN,EACA,WAAAC,EACA,QAAAW,EACA,QAAAC,EACA,cAAAX,CAAA,CAEJ,2MC9HA,MAAMN,EAAiC,CAAC,GAAI,GAAI,GAAI,GAAG,EAEvD,eAAekB,EAAmBC,EAA6B,CAC7DtB,EAAkB,MAAQsB,CAC5B,6kECHA,MAAMC,EAASC,KAETC,EAAQC,EAAS,CACrB,qBAAsB,EAAA,CACvB,EAED,eAAeC,GAA2B,CACxCF,EAAM,qBAAuB,GACvB,MAAAG,EAAa,MAAMC,KACzB,MAAMN,EAAO,KAAKO,EAAcF,CAAU,CAAC,EAC3CH,EAAM,qBAAuB,EAC/B,28BCHQM,GAAA,CAAE,MAAO,WAAA,CAAa,EAE9B,MAAM/E,EAA4BC,IAE5BwE,EAAQC,EAAS,CACrB,aAAc,EAAA,CACf,EAEKM,EAAcxD,EAAI,EAAE,EAEpByD,EAAoBP,EACxBxB,GAAwB,CACtB,MAAO,YACP,EAAG8B,CAAA,CACJ,CAAA,EAGGE,EAAY/E,EAAS,IAAM8E,EAAkB,cAAgB,IAAI,EAEvE,eAAeE,GAAiB,CAC9B,MAAMF,EAAkB,SAC1B,CAEA,SAASG,EAAWC,EAAa,CAC/BZ,EAAM,aAAe,GACrBO,EAAY,MAAQA,EAAY,QAAUK,EAAM,GAAKA,CACvD"}