gitea源码

ViewFileTreeItem.vue 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. <script lang="ts" setup>
  2. import {SvgIcon} from '../svg.ts';
  3. import {isPlainClick} from '../utils/dom.ts';
  4. import {shallowRef} from 'vue';
  5. import {type createViewFileTreeStore} from './ViewFileTreeStore.ts';
  6. type Item = {
  7. entryName: string;
  8. entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
  9. entryIcon: string;
  10. entryIconOpen: string;
  11. fullPath: string;
  12. submoduleUrl?: string;
  13. children?: Item[];
  14. };
  15. const props = defineProps<{
  16. item: Item,
  17. store: ReturnType<typeof createViewFileTreeStore>
  18. }>();
  19. const store = props.store;
  20. const isLoading = shallowRef(false);
  21. const children = shallowRef(props.item.children);
  22. const collapsed = shallowRef(!props.item.children);
  23. const doLoadChildren = async () => {
  24. collapsed.value = !collapsed.value;
  25. if (!collapsed.value) {
  26. isLoading.value = true;
  27. try {
  28. children.value = await store.loadChildren(props.item.fullPath);
  29. } finally {
  30. isLoading.value = false;
  31. }
  32. }
  33. };
  34. const onItemClick = (e: MouseEvent) => {
  35. // only handle the click event with page partial reloading if the user didn't press any special key
  36. // let browsers handle special keys like "Ctrl+Click"
  37. if (!isPlainClick(e)) return;
  38. e.preventDefault();
  39. if (props.item.entryMode === 'tree') doLoadChildren();
  40. store.navigateTreeView(props.item.fullPath);
  41. };
  42. </script>
  43. <template>
  44. <a
  45. class="tree-item silenced"
  46. :class="{
  47. 'selected': store.selectedItem === item.fullPath,
  48. 'type-submodule': item.entryMode === 'commit',
  49. 'type-directory': item.entryMode === 'tree',
  50. 'type-symlink': item.entryMode === 'symlink',
  51. 'type-file': item.entryMode === 'blob' || item.entryMode === 'exec',
  52. }"
  53. :title="item.entryName"
  54. :href="store.buildTreePathWebUrl(item.fullPath)"
  55. @click.stop="onItemClick"
  56. >
  57. <div v-if="item.entryMode === 'tree'" class="item-toggle">
  58. <SvgIcon v-if="isLoading" name="octicon-sync" class="circular-spin"/>
  59. <SvgIcon v-else :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'" @click.stop.prevent="doLoadChildren"/>
  60. </div>
  61. <div class="item-content">
  62. <!-- eslint-disable-next-line vue/no-v-html -->
  63. <span class="tw-contents" v-html="(!collapsed && item.entryIconOpen) ? item.entryIconOpen : item.entryIcon"/>
  64. <span class="gt-ellipsis">{{ item.entryName }}</span>
  65. </div>
  66. </a>
  67. <div v-if="children?.length" v-show="!collapsed" class="sub-items">
  68. <ViewFileTreeItem v-for="childItem in children" :key="childItem.entryName" :item="childItem" :store="store"/>
  69. </div>
  70. </template>
  71. <style scoped>
  72. .sub-items {
  73. display: flex;
  74. flex-direction: column;
  75. gap: 1px;
  76. margin-left: 14px;
  77. border-left: 1px solid var(--color-secondary);
  78. }
  79. .tree-item.selected {
  80. color: var(--color-text);
  81. background: var(--color-active);
  82. border-radius: 4px;
  83. }
  84. .tree-item.type-directory {
  85. user-select: none;
  86. }
  87. .tree-item {
  88. display: grid;
  89. grid-template-columns: 16px 1fr;
  90. grid-template-areas: "toggle content";
  91. gap: 0.25em;
  92. padding: 6px;
  93. }
  94. .tree-item:hover {
  95. color: var(--color-text);
  96. background: var(--color-hover);
  97. border-radius: 4px;
  98. cursor: pointer;
  99. }
  100. .item-toggle {
  101. grid-area: toggle;
  102. display: flex;
  103. align-items: center;
  104. }
  105. .item-content {
  106. grid-area: content;
  107. display: flex;
  108. align-items: center;
  109. gap: 0.5em;
  110. text-overflow: ellipsis;
  111. min-width: 0;
  112. }
  113. </style>