kopia lustrzana https://github.com/elk-zone/elk
				
				
				
			refactor(command): use dialog (#352)
							rodzic
							
								
									f249087a95
								
							
						
					
					
						commit
						462e85dad0
					
				
							
								
								
									
										1
									
								
								app.vue
								
								
								
								
							
							
						
						
									
										1
									
								
								app.vue
								
								
								
								
							|  | @ -1,6 +1,7 @@ | |||
| <script setup lang="ts"> | ||||
| setupPageHeader() | ||||
| await setupI18n() | ||||
| provideGlobalCommands() | ||||
| 
 | ||||
| // We want to trigger rerendering the page when account changes | ||||
| const key = computed(() => `${currentServer.value}:${currentUser.value?.account.id || ''}`) | ||||
|  |  | |||
|  | @ -1,31 +1,21 @@ | |||
| <script setup lang="ts"> | ||||
| import type { CommandScope, QueryIndexedCommand } from '@/composables/command' | ||||
| 
 | ||||
| const isMac = useIsMac() | ||||
| const emit = defineEmits<{ | ||||
|   (event: 'close'): void | ||||
| }>() | ||||
| 
 | ||||
| const registry = useCommandRegistry() | ||||
| 
 | ||||
| const inputEl = $ref<HTMLInputElement>() | ||||
| const resultEl = $ref<HTMLDivElement>() | ||||
| 
 | ||||
| let show = $ref(false) | ||||
| let scopes = $ref<CommandScope[]>([]) | ||||
| let input = $ref('') | ||||
| const scopes = $ref<CommandScope[]>([]) | ||||
| let input = $(commandPanelInput) | ||||
| 
 | ||||
| // listen to ctrl+/ on windows/linux or cmd+/ on mac | ||||
| useEventListener('keydown', async (e: KeyboardEvent) => { | ||||
|   if (e.key === '/' && (isMac.value ? e.metaKey : e.ctrlKey)) { | ||||
|     e.preventDefault() | ||||
|     show = true | ||||
|     scopes = [] | ||||
|     input = '>' | ||||
|     await nextTick() | ||||
|     inputEl?.focus() | ||||
|   } | ||||
| onMounted(() => { | ||||
|   inputEl?.focus() | ||||
| }) | ||||
| onKeyStroke('Escape', (e) => { | ||||
|   e.preventDefault() | ||||
|   show = false | ||||
| }, { target: document }) | ||||
| 
 | ||||
| const commandMode = $computed(() => input.startsWith('>')) | ||||
| const result = $computed(() => commandMode | ||||
|  | @ -42,7 +32,7 @@ const findItemEl = (index: number) => | |||
| const onCommandActivate = (item: QueryIndexedCommand) => { | ||||
|   if (item.onActivate) { | ||||
|     item.onActivate() | ||||
|     show = false | ||||
|     emit('close') | ||||
|   } | ||||
|   else if (item.onComplete) { | ||||
|     scopes.push(item.onComplete()) | ||||
|  | @ -137,117 +127,86 @@ const onKeyDown = (e: KeyboardEvent) => { | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <!-- Overlay --> | ||||
|   <Transition | ||||
|     enter-active-class="transition duration-200 ease-out" | ||||
|     enter-from-class="transform opacity-0" | ||||
|     enter-to-class="transform opacity-100" | ||||
|     leave-active-class="transition duration-100 ease-in" | ||||
|     leave-from-class="transform opacity-100" | ||||
|     leave-to-class="transform opacity-0" | ||||
|   > | ||||
|     <div | ||||
|       v-if="show" | ||||
|       class="z-100 fixed inset-0 opacity-70 bg-base" | ||||
|       @click="show = false" | ||||
|     /> | ||||
|   </Transition> | ||||
|   <div class="flex flex-col w-50vw max-w-180 h-50vh max-h-120"> | ||||
|     <!-- Input --> | ||||
|     <label class="flex mx-3 my-1 items-center"> | ||||
|       <div mx-1 i-ri:search-line /> | ||||
| 
 | ||||
|   <!-- Panel --> | ||||
|   <Transition | ||||
|     enter-active-class="transition duration-65 ease-out" | ||||
|     enter-from-class="transform scale-95" | ||||
|     enter-to-class="transform scale-100" | ||||
|     leave-active-class="transition duration-100 ease-in" | ||||
|     leave-from-class="transform scale-100" | ||||
|     leave-to-class="transform scale-95" | ||||
|   > | ||||
|     <div v-if="show" class="z-100 fixed inset-0 grid place-items-center pointer-events-none"> | ||||
|       <div | ||||
|         class="flex flex-col w-50vw h-50vh rounded-md bg-base shadow-lg pointer-events-auto" | ||||
|         border="1 base" | ||||
|       <div v-for="scope in scopes" :key="scope.id" class="flex items-center mx-1 gap-2"> | ||||
|         <div class="text-sm">{{ scope.display }}</div> | ||||
|         <span class="text-secondary">/</span> | ||||
|       </div> | ||||
| 
 | ||||
|       <input | ||||
|         ref="inputEl" | ||||
|         v-model="input" | ||||
|         class="focus:outline-none flex-1 p-2 rounded bg-base" | ||||
|         placeholder="Search" | ||||
|         @keydown="onKeyDown" | ||||
|       > | ||||
|         <!-- Input --> | ||||
|         <label class="flex mx-3 my-1 items-center"> | ||||
|           <div mx-1 i-ri:search-line /> | ||||
| 
 | ||||
|           <div v-for="scope in scopes" :key="scope.id" class="flex items-center mx-1 gap-2"> | ||||
|             <div class="text-sm">{{ scope.display }}</div> | ||||
|             <span class="text-secondary">/</span> | ||||
|           </div> | ||||
|       <CommandKey name="Escape" /> | ||||
|     </label> | ||||
| 
 | ||||
|           <input | ||||
|             ref="inputEl" | ||||
|             v-model="input" | ||||
|             class="focus:outline-none flex-1 p-2 rounded bg-base" | ||||
|             placeholder="Search" | ||||
|             @keydown="onKeyDown" | ||||
|     <div class="w-full border-b-1 border-base" /> | ||||
| 
 | ||||
|     <!-- Results --> | ||||
|     <div ref="resultEl" class="flex-1 mx-1 overflow-y-auto"> | ||||
|       <template v-for="[scope, group] in result.grouped" :key="scope"> | ||||
|         <div class="mt-2 px-2 py-1 text-sm text-secondary"> | ||||
|           {{ scope }} | ||||
|         </div> | ||||
| 
 | ||||
|         <template v-for="cmd in group" :key="cmd.index"> | ||||
|           <div | ||||
|             class="flex px-3 py-2 my-1 items-center rounded-lg hover:bg-active transition-all duration-65 ease-in-out cursor-pointer scroll-m-10" | ||||
|             :class="{ 'bg-active': active === cmd.index }" | ||||
|             :data-index="cmd.index" | ||||
|             @click="onCommandActivate(cmd)" | ||||
|           > | ||||
|             <div v-if="cmd.icon" mr-2 :class="cmd.icon" /> | ||||
| 
 | ||||
|           <CommandKey name="Escape" /> | ||||
|         </label> | ||||
| 
 | ||||
|         <div class="w-full border-b-1 border-base" /> | ||||
| 
 | ||||
|         <!-- Results --> | ||||
|         <div ref="resultEl" class="flex-1 mx-1 overflow-y-auto"> | ||||
|           <template v-for="[scope, group] in result.grouped" :key="scope"> | ||||
|             <div class="mt-2 px-2 py-1 text-sm text-secondary"> | ||||
|               {{ scope }} | ||||
|             <div class="flex-1 flex items-baseline gap-2"> | ||||
|               <div :class="{ 'font-medium': active === cmd.index }"> | ||||
|                 {{ cmd.name }} | ||||
|               </div> | ||||
|               <div v-if="cmd.description" class="text-xs text-secondary"> | ||||
|                 {{ cmd.description }} | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <template v-for="cmd in group" :key="cmd.index"> | ||||
|               <div | ||||
|                 class="flex px-3 py-2 my-1 items-center rounded-lg hover:bg-active transition-all duration-65 ease-in-out cursor-pointer scroll-m-10" | ||||
|                 :class="{ 'bg-active': active === cmd.index }" | ||||
|                 :data-index="cmd.index" | ||||
|                 @click="onCommandActivate(cmd)" | ||||
|               > | ||||
|                 <div v-if="cmd.icon" mr-2 :class="cmd.icon" /> | ||||
| 
 | ||||
|                 <div class="flex-1 flex items-baseline gap-2"> | ||||
|                   <div :class="{ 'font-medium': active === cmd.index }"> | ||||
|                     {{ cmd.name }} | ||||
|                   </div> | ||||
|                   <div v-if="cmd.description" class="text-xs text-secondary"> | ||||
|                     {{ cmd.description }} | ||||
|                   </div> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div | ||||
|                   v-if="cmd.onComplete" | ||||
|                   class="flex items-center gap-1 transition-all duration-65 ease-in-out" | ||||
|                   :class="active === cmd.index ? 'opacity-100' : 'opacity-0'" | ||||
|                 > | ||||
|                   <div class="text-xs text-secondary"> | ||||
|                     {{ $t('command.complete') }} | ||||
|                   </div> | ||||
|                   <CommandKey name="Tab" /> | ||||
|                 </div> | ||||
|                 <div | ||||
|                   v-if="cmd.onActivate" | ||||
|                   class="flex items-center gap-1 transition-all duration-65 ease-in-out" | ||||
|                   :class="active === cmd.index ? 'opacity-100' : 'opacity-0'" | ||||
|                 > | ||||
|                   <div class="text-xs text-secondary"> | ||||
|                     {{ $t('command.activate') }} | ||||
|                   </div> | ||||
|                   <CommandKey name="Enter" /> | ||||
|                 </div> | ||||
|             <div | ||||
|               v-if="cmd.onComplete" | ||||
|               class="flex items-center gap-1 transition-all duration-65 ease-in-out" | ||||
|               :class="active === cmd.index ? 'opacity-100' : 'opacity-0'" | ||||
|             > | ||||
|               <div class="text-xs text-secondary"> | ||||
|                 {{ $t('command.complete') }} | ||||
|               </div> | ||||
|             </template> | ||||
|           </template> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="w-full border-b-1 border-base" /> | ||||
| 
 | ||||
|         <!-- Footer --> | ||||
|         <div class="flex items-center px-3 py-1 text-xs"> | ||||
|           <div i-ri:lightbulb-flash-line /> Tip: Use | ||||
|           <!-- <CommandKey name="Ctrl+K" /> to search, --> | ||||
|           <CommandKey name="Ctrl+/" /> to activate command mode. | ||||
|         </div> | ||||
|       </div> | ||||
|               <CommandKey name="Tab" /> | ||||
|             </div> | ||||
|             <div | ||||
|               v-if="cmd.onActivate" | ||||
|               class="flex items-center gap-1 transition-all duration-65 ease-in-out" | ||||
|               :class="active === cmd.index ? 'opacity-100' : 'opacity-0'" | ||||
|             > | ||||
|               <div class="text-xs text-secondary"> | ||||
|                 {{ $t('command.activate') }} | ||||
|               </div> | ||||
|               <CommandKey name="Enter" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </template> | ||||
|       </template> | ||||
|     </div> | ||||
|   </Transition> | ||||
| 
 | ||||
|     <div class="w-full border-b-1 border-base" /> | ||||
| 
 | ||||
|     <!-- Footer --> | ||||
|     <div class="flex items-center px-3 py-1 text-xs"> | ||||
|       <div i-ri:lightbulb-flash-line /> Tip: Use | ||||
|       <!-- <CommandKey name="Ctrl+K" /> to search, --> | ||||
|       <CommandKey name="Ctrl+/" /> to activate command mode. | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,7 +0,0 @@ | |||
| <script setup lang="ts"> | ||||
| provideGlobalCommands() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <CommandPanel /> | ||||
| </template> | ||||
|  | @ -1,11 +1,23 @@ | |||
| <script setup lang="ts"> | ||||
| import { | ||||
|   isCommandPanelOpen, | ||||
|   isEditHistoryDialogOpen, | ||||
|   isMediaPreviewOpen, | ||||
|   isPreviewHelpOpen, | ||||
|   isPublishDialogOpen, | ||||
|   isSigninDialogOpen, | ||||
| } from '~/composables/dialog' | ||||
| 
 | ||||
| const isMac = useIsMac() | ||||
| 
 | ||||
| // TODO: temporary, await for keybind system | ||||
| // listen to ctrl+/ on windows/linux or cmd+/ on mac | ||||
| useEventListener('keydown', (e: KeyboardEvent) => { | ||||
|   if (e.key === '/' && (isMac.value ? e.metaKey : e.ctrlKey)) { | ||||
|     e.preventDefault() | ||||
|     openCommandPanel(true) | ||||
|   } | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -30,4 +42,7 @@ import { | |||
|   <ModalDialog v-model="isEditHistoryDialogOpen"> | ||||
|     <StatusEditPreview :edit="statusEdit" /> | ||||
|   </ModalDialog> | ||||
|   <ModalDialog v-model="isCommandPanelOpen" max-w-fit flex> | ||||
|     <CommandPanel @close="closeCommandPanel()" /> | ||||
|   </ModalDialog> | ||||
| </template> | ||||
|  |  | |||
|  | @ -281,7 +281,7 @@ export const provideGlobalCommands = () => { | |||
|     visible: () => users.value.length > 1, | ||||
| 
 | ||||
|     name: () => t('action.switch_account'), | ||||
|     description: t('command.switch_account_desc'), | ||||
|     description: () => t('command.switch_account_desc'), | ||||
|     icon: 'i-ri:user-shared-line', | ||||
| 
 | ||||
|     onComplete: () => ({ | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ export const mediaPreviewIndex = ref(0) | |||
| export const statusEdit = ref<StatusEdit>() | ||||
| export const dialogDraftKey = ref<string>() | ||||
| 
 | ||||
| export const commandPanelInput = ref('') | ||||
| 
 | ||||
| export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, !process.mock) | ||||
| export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false) | ||||
| 
 | ||||
|  | @ -16,6 +18,7 @@ export const isPublishDialogOpen = ref(false) | |||
| export const isMediaPreviewOpen = ref(false) | ||||
| export const isEditHistoryDialogOpen = ref(false) | ||||
| export const isPreviewHelpOpen = ref(isFirstVisit.value) | ||||
| export const isCommandPanelOpen = ref(false) | ||||
| 
 | ||||
| export const toggleZenMode = useToggle(isZenMode) | ||||
| 
 | ||||
|  | @ -72,3 +75,12 @@ export function openPreviewHelp() { | |||
| export function closePreviewHelp() { | ||||
|   isPreviewHelpOpen.value = false | ||||
| } | ||||
| 
 | ||||
| export function openCommandPanel(isCommandMode = false) { | ||||
|   commandPanelInput.value = isCommandMode ? '>' : '' | ||||
|   isCommandPanelOpen.value = true | ||||
| } | ||||
| 
 | ||||
| export function closeCommandPanel() { | ||||
|   isCommandPanelOpen.value = false | ||||
| } | ||||
|  |  | |||
|  | @ -51,6 +51,5 @@ | |||
|       </aside> | ||||
|     </main> | ||||
|     <ModalContainer /> | ||||
|     <CommandRoot /> | ||||
|   </div> | ||||
| </template> | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 QiroNT
						QiroNT