magic search bar

This commit is contained in:
Andras Bacsai
2023-05-11 15:20:02 +02:00
parent 8e1c6d2bd2
commit 70d032ff23
17 changed files with 686 additions and 481 deletions

View File

@@ -1,68 +1,339 @@
@props(['data' => []])
<div x-data="magicsearchbar">
<input x-model="search" class="w-96" x-on:click="open = true" x-on:click.outside="close"
placeholder="🪄 Add / find anything" />
<div x-cloak x-show="open" class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
{{-- Main --}}
<input x-ref="mainSearch" x-cloak x-show="!serverMenu && !destinationMenu && !projectMenu && !environmentMenu"
x-model="mainSearch" class="w-96" x-on:click="checkMainMenu" x-on:click.outside="closeMainMenu"
placeholder="🪄 Search for anything... magically..." />
<div x-cloak x-show="mainMenu" class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<template x-for="item in filteredItems" :key="item.name">
<div x-on:click="execute(item.action)" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded" x-show="item.type" x-text="item.type"></span>
<div x-on:click="await next('server',item.name, item.disabled ?? false)"
:class="item.disabled && 'text-neutral-500 bg-neutral-900 hover:bg-neutral-900 cursor-not-allowed opacity-60'"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-green-600 rounded" x-show="item.type === 'Add'"
x-text="item.type"></span>
<span class="px-2 mr-1 text-xs bg-purple-600 rounded" x-show="item.type === 'Jump'"
x-text="item.type"></span>
<span x-text="item.name"></span>
</div>
</template>
</div>
{{-- Servers --}}
<div x-cloak x-show="serverMenu" x-on:click.outside="closeServerMenu">
<input x-ref="serverSearch" x-model="serverSearch" class="w-96" placeholder="Select a server" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<template x-for="server in filteredServers" :key="server.name ?? server">
<div x-on:click="await next('destination',server.uuid)"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-600 rounded">Server</span>
<span x-text="server.name"></span>
</div>
</template>
</div>
</div>
{{-- Destinations --}}
<div x-cloak x-show="destinationMenu" x-on:click.outside="closeDestinationMenu">
<input x-ref="destinationSearch" x-model="destinationSearch" class="w-96"
placeholder="Select a destination" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<template x-for="destination in filteredDestinations" :key="destination.name ?? destination">
<div x-on:click="await next('project',destination.uuid)"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Destination</span>
<span x-text="destination.name"></span>
</div>
</template>
</div>
</div>
{{-- Projects --}}
<div x-cloak x-show="projectMenu" x-on:click.outside="closeProjectMenu">
<input x-ref="projectSearch" x-model="projectSearch" class="w-96" placeholder="Type your project name..." />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<div x-on:click="await newProject" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span>New Project</span>
<span x-text="projectSearch"></span>
</div>
<template x-for="project in filteredProjects" :key="project.name ?? project">
<div x-on:click="await next('environment',project.uuid)"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Project</span>
<span x-text="project.name"></span>
</div>
</template>
</div>
</div>
{{-- Environments --}}
<div x-cloak x-show="environmentMenu" x-on:click.outside="closeEnvironmentMenu">
<input x-ref="environmentSearch" x-model="environmentSearch" class="w-96"
placeholder="Select a environment" />
<div class="absolute text-sm top-11 w-[25rem] bg-neutral-800">
<div x-on:click="await newEnvironment" class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span>New Environment</span>
<span x-text="environmentSearch"></span>
</div>
<template x-for="environment in filteredEnvironments" :key="environment.name ?? environment">
<div x-on:click="await next('jump',environment.name)"
class="py-2 pl-4 cursor-pointer hover:bg-neutral-700">
<span class="px-2 mr-1 text-xs bg-purple-700 rounded">Env</span>
<span x-text="environment.name"></span>
</div>
</template>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
const data = @js($data);
console.log(data)
Alpine.data('magicsearchbar', () => ({
open: false,
search: '',
items: [{
name: 'Public Repository',
type: 'add',
tags: 'application,public,repository',
action: 'public-repo',
}, {
name: 'Private Repository (with GitHub App)',
type: 'add',
tags: 'application,private,repository',
action: 'github-private-repo-app'
}, {
name: 'Private Repository (with Deploy Key)',
type: 'add',
tags: 'application,private,repository',
action: 'github-private-repo-deploy-key'
}, {
name: 'Database',
type: 'add',
tags: 'data,database,mysql,postgres,sql,sqlite,redis,mongodb,maria,percona',
action: 'database'
}],
close() {
this.open = false
this.search = ''
},
filteredItems() {
if (this.search === '') return this.items
return this.items.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase())
init() {
this.$watch('mainMenu', (value) => {
if (value) this.$refs.mainSearch.focus()
})
this.$watch('serverMenu', (value) => {
this.$nextTick(() => {
this.$refs.serverSearch.focus()
})
})
this.$watch('destinationMenu', (value) => {
this.$nextTick(() => {
this.$refs.destinationSearch.focus()
})
})
this.$watch('projectMenu', (value) => {
this.$nextTick(() => {
this.$refs.projectSearch.focus()
})
})
this.$watch('environmentMenu', (value) => {
this.$nextTick(() => {
this.$refs.environmentSearch.focus()
})
})
},
execute(action) {
mainMenu: false,
serverMenu: false,
destinationMenu: false,
projectMenu: false,
environmentMenu: false,
mainSearch: '',
serverSearch: '',
destinationSearch: '',
projectSearch: '',
environmentSearch: '',
selectedAction: '',
selectedServer: '',
selectedDestination: '',
selectedProject: '',
selectedEnvironment: '',
servers: ['Loading...'],
destinations: ['Loading...'],
projects: ['Loading...'],
environments: ['Loading...'],
items: [{
name: 'Public Repository',
type: 'Add',
tags: 'application,public,repository',
},
{
name: 'Private Repository (with GitHub App)',
type: 'Add',
tags: 'application,private,repository',
},
{
name: 'Private Repository (with Deploy Key)',
type: 'Add',
tags: 'application,private,repository',
},
{
name: 'Database',
type: 'Add',
tags: 'data,database,mysql,postgres,sql,sqlite,redis,mongodb,maria,percona',
disabled: true,
},
{
name: 'Servers',
type: 'Jump',
}
],
checkMainMenu() {
if (this.serverMenu) return
this.mainMenu = true
},
closeMainMenu() {
this.mainMenu = false
this.mainSearch = ''
},
closeServerMenu() {
this.serverMenu = false
this.serverSearch = ''
},
closeDestinationMenu() {
this.destinationMenu = false
this.destinationSearch = ''
},
closeProjectMenu() {
this.projectMenu = false
this.projectSearch = ''
},
closeEnvironmentMenu() {
this.environmentMenu = false
this.environmentSearch = ''
},
filteredItems() {
if (this.mainSearch === '') return this.items
return this.items.filter(item => {
return item.name.toLowerCase().includes(this.mainSearch.toLowerCase())
})
},
filteredServers() {
if (this.serverSearch === '') return this.servers
return this.servers.filter(server => {
return server.name.toLowerCase().includes(this.serverSearch
.toLowerCase())
})
},
filteredDestinations() {
if (this.destinationSearch === '') return this.destinations
return this.destinations.filter(destination => {
return destination.name.toLowerCase().includes(this.destinationSearch
.toLowerCase())
})
},
filteredProjects() {
if (this.projectSearch === '') return this.projects
return this.projects.filter(project => {
return project.name.toLowerCase().includes(this.projectSearch
.toLowerCase())
})
},
filteredEnvironments() {
if (this.environmentSearch === '') return this.environments
return this.environments.filter(environment => {
return environment.name.toLowerCase().includes(this.environmentSearch
.toLowerCase())
})
},
async newProject() {
const response = await fetch('/magic?server=' + this.selectedServer +
'&destination=' + this.selectedDestination +
'&project=new&name=' + this.projectSearch);
if (response.ok) {
const {
project_uuid
} = await response.json();
this.next('environment', project_uuid)
this.next('jump', 'production')
}
},
async newEnvironment() {
const response = await fetch('/magic?server=' + this.selectedServer +
'&destination=' + this.selectedDestination +
'&project=' + this.selectedProject + '&environment=new&name=' + this
.environmentSearch);
if (response.ok) {
this.next('jump', this.environmentSearch)
}
},
async next(action, id, isDisabled) {
if (isDisabled) return
let response = null
switch (action) {
case 'public-repo':
window.location.href = '/project/new/public-repository'
case 'server':
this.mainMenu = false
this.serverMenu = true
this.items.find((item, index) => {
if (item.name.toLowerCase() === id
.toLowerCase()) {
return this.selectedAction = index
}
})
response = await fetch('/magic?servers=true');
if (response.ok) {
const {
servers
} = await response.json();
this.servers = servers;
}
break
case 'github-private-repo-app':
window.location.href = '/project/new/github-private-repository'
case 'destination':
if (this.items[this.selectedAction].type === "Jump") {
return window.location = '/server/' + id
}
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = true
this.selectedServer = id
response = await fetch('/magic?server=' + this
.selectedServer +
'&destinations=true');
if (response.ok) {
const {
destinations
} = await response.json();
this.destinations = destinations;
}
break
case 'github-private-repo-deploy-key':
window.location.href = '/project/new/github-private-repository-deploy-key'
case 'project':
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = false
this.projectMenu = true
this.selectedDestination = id
response = await fetch('/magic?server=' + this
.selectedServer +
'&destination=' + this.selectedDestination +
'&projects=true');
if (response.ok) {
const {
projects
} = await response.json();
this.projects = projects;
}
break
case 'database':
window.location.href = '/database/new'
case 'environment':
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = false
this.projectMenu = false
this.environmentMenu = true
this.selectedProject = id
response = await fetch('/magic?server=' + this
.selectedServer +
'&destination=' + this.selectedDestination +
'&project=' + this
.selectedProject + '&environments=true');
if (response.ok) {
const {
environments
} = await response.json();
this.environments = environments;
}
break
case 'jump':
this.mainMenu = false
this.serverMenu = false
this.destinationMenu = false
this.projectMenu = false
this.environmentMenu = false
this.selectedEnvironment = id
if (this.selectedAction === 0) {
window.location =
`/project/${this.selectedProject}/${this.selectedEnvironment}/new?type=public&destination=${this.selectedDestination}`
} else if (this.selectedAction === 1) {
window.location =
`/project/${this.selectedProject}/${this.selectedEnvironment}/new?type=private-gh-app&destination=${this.selectedDestination}`
} else if (this.selectedAction === 2) {
window.location =
`/project/${this.selectedProject}/${this.selectedEnvironment}/new?type=private-deploy-key&destination=${this.selectedDestination}`
} else if (this.selectedAction === 3) {
console.log('new Database')
}
break
}
}

View File

@@ -1,7 +1,5 @@
<x-layout>
<h1>Projects <a href="{{ route('project.new') }}">
<x-inputs.button>New</x-inputs.button>
</a></h1>
<h1>Projects </h1>
@forelse ($projects as $project)
<a href="{{ route('project.environments', [$project->uuid]) }}">{{ data_get($project, 'name') }}</a>
@empty

View File

@@ -1,58 +1,17 @@
<div>
@if ($servers->count() > 0)
<h1>Choose a server</h1>
@endif
@forelse ($servers as $server)
@if ($chosenServer && $chosenServer['id'] === $server->id)
<x-inputs.button class="bg-blue-500" wire:click="chooseServer({{ $server }})">{{ $server->name }}
</x-inputs.button>
@else
<x-inputs.button wire:click="chooseServer({{ $server }})">{{ $server->name }}</x-inputs.button>
@endif
@empty
No servers found.
<p>Did you forget to add a destination on the server?</p>
@endforelse
@isset($chosenServer)
@if ($standalone_docker->count() > 0 || $swarm_docker->count() > 0)
<h1>Choose a destination</h1>
<div>
@foreach ($standalone_docker as $standalone)
@if ($chosenDestination?->uuid == $standalone->uuid)
<x-inputs.button class="bg-blue-500"
wire:click="setDestination('{{ $standalone->uuid }}','StandaloneDocker')">
{{ $standalone->network }}</x-inputs.button>
@else
<x-inputs.button wire:click="setDestination('{{ $standalone->uuid }}','StandaloneDocker')">
{{ $standalone->network }}</x-inputs.button>
@endif
@endforeach
@foreach ($swarm_docker as $standalone)
@if ($chosenDestination?->uuid == $standalone->uuid)
<x-inputs.button class="bg-blue-500"
wire:click="setDestination('{{ $standalone->uuid }}','SwarmDocker')">
{{ $standalone->network }}</x-inputs.button>
@else
<x-inputs.button wire:click="setDestination('{{ $standalone->uuid }}','SwarmDocker')">
{{ $standalone->uuid }}</x-inputs.button>
@endif
@endforeach
</div>
<div>
<a href="{{ route('destination.new', ['server_id' => $chosenServer['id']]) }}">Add
a new
destination</a>
</div>
@else
<h1>No destinations found on this server.</h1>
<a href="{{ route('destination.new', ['server_id' => $chosenServer['id']]) }}">Add
a
destination</a>
@endif
@endisset
@isset($chosenDestination)
<div>
<h1>Select a private key</h1>
@foreach ($private_keys as $key)
@if ($private_key_id == $key->id)
<x-inputs.button class="bg-blue-500" wire:click.defer="setPrivateKey('{{ $key->id }}')">
{{ $key->name }}</x-inputs.button>
@else
<x-inputs.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
</x-inputs.button>
@endif
@endforeach
</div>
@isset($private_key_id)
<h1>Choose a repository</h1>
<form wire:submit.prevent='submit'>
<div class="flex items-end gap-2 pb-2">
@@ -68,17 +27,5 @@
Submit
</x-inputs.button>
</form>
<div>
<h1>Select a private key</h1>
@foreach ($private_keys as $key)
@if ($private_key_id == $key->id)
<x-inputs.button class="bg-blue-500" wire:click.defer="setPrivateKey('{{ $key->id }}')">
{{ $key->name }}</x-inputs.button>
@else
<x-inputs.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
</x-inputs.button>
@endif
@endforeach
</div>
@endisset
</div>

View File

@@ -35,48 +35,7 @@
@endif
@endforeach
</select>
<x-inputs.button wire:click="loadServers">Select Branch</x-inputs.button>
@endif
</div>
<div>
@if ($servers->count() > 0)
<h3>Choose a Server</h3>
<select wire:model.defer="selected_server_id">
<option disabled>Choose a server</option>
@foreach ($servers as $server)
@if ($loop->first)
<option selected value="{{ data_get($server, 'id') }}">{{ data_get($server, 'name') }}
</option>
@else
<option value="{{ data_get($server, 'id') }}">{{ data_get($server, 'name') }}</option>
@endif
@endforeach
</select>
<x-inputs.button wire:click="loadDestinations">Select Server</x-inputs.button>
@endif
</div>
<div>
@if ($destinations->count() > 0)
<h3>Choose a Destination</h3>
<select wire:model.defer="selected_destination_id">
<option disabled>Choose a destination</option>
@foreach ($destinations as $destination)
@if ($loop->first)
<option selected value="{{ data_get($destination, 'id') }}">
{{ data_get($destination, 'network') }}</option>
@else
<option value="{{ data_get($destination, 'id') }}">{{ data_get($destination, 'network') }}
</option>
@endif
@endforeach
</select>
<x-inputs.button wire:click="submit">Select Destination</x-inputs.button>
@else
<h1>No destinations found on this server.</h1>
<a href="{{ route('destination.new', ['server_id' => $selected_server_id]) }}">Add
a
destination</a>
<x-inputs.button wire:click="submit">Save</x-inputs.button>
@endif
</div>
@else

View File

@@ -1,72 +1,17 @@
<div>
@if ($servers->count() > 0)
<h1>Choose a server</h1>
@endif
@forelse ($servers as $server)
@if ($chosenServer && $chosenServer['id'] === $server->id)
<x-inputs.button class="bg-blue-500" wire:click="chooseServer({{ $server }})">{{ $server->name }}
</x-inputs.button>
@else
<x-inputs.button wire:click="chooseServer({{ $server }})">{{ $server->name }}</x-inputs.button>
@endif
@empty
No servers found.
<p>Did you forget to add a destination on the server?</p>
@endforelse
@isset($chosenServer)
@if ($standalone_docker->count() > 0 || $swarm_docker->count() > 0)
<h1>Choose a destination</h1>
<div>
@foreach ($standalone_docker as $standalone)
@if ($chosenDestination?->uuid == $standalone->uuid)
<x-inputs.button class="bg-blue-500"
wire:click="setDestination('{{ $standalone->uuid }}','StandaloneDocker')">
{{ $standalone->network }}</x-inputs.button>
@else
<x-inputs.button wire:click="setDestination('{{ $standalone->uuid }}','StandaloneDocker')">
{{ $standalone->network }}</x-inputs.button>
@endif
@endforeach
@foreach ($swarm_docker as $standalone)
@if ($chosenDestination?->uuid == $standalone->uuid)
<x-inputs.button class="bg-blue-500"
wire:click="setDestination('{{ $standalone->uuid }}','SwarmDocker')">
{{ $standalone->network }}</x-inputs.button>
@else
<x-inputs.button wire:click="setDestination('{{ $standalone->uuid }}','SwarmDocker')">
{{ $standalone->uuid }}</x-inputs.button>
@endif
@endforeach
</div>
<div>
<a href="{{ route('destination.new', ['server_id' => $chosenServer['id']]) }}">Add
a new
destination</a>
</div>
@else
<h1>No destinations found on this server.</h1>
<a href="{{ route('destination.new', ['server_id' => $chosenServer['id']]) }}">Add
a
destination</a>
@endif
@endisset
@isset($chosenDestination)
<h1>Choose a repository</h1>
<form class="flex flex-col gap-2 w-96" wire:submit.prevent='submit'>
<h1>Choose a public repository</h1>
<form class="flex flex-col gap-2 w-96" wire:submit.prevent='submit'>
<x-inputs.input instantSave type="checkbox" id="is_static" label="Is it a static site?" />
<div class="flex gap-2">
<x-inputs.input class="w-96" id="repository_url" label="Repository URL" />
<x-inputs.input instantSave type="checkbox" id="is_static" label="Static Site?" />
@if ($is_static)
<x-inputs.input id="publish_directory" label="Publish Directory" />
@else
<x-inputs.input type="number" id="port" label="Port" :readonly="$is_static" />
@endif
<x-inputs.button type="submit">
Submit
</x-inputs.button>
</form>
@endisset
</div>
<x-inputs.button type="submit">
Submit
</x-inputs.button>
</form>
</div>

View File

@@ -1,30 +1,9 @@
<x-layout>
@if ($type === 'project')
<h1>New Project</h1>
@elseif ($type === 'resource')
<h1>New Resource</h1>
@if ($type === 'public')
<livewire:project.new.public-git-repository :type="$type" />
@elseif ($type === 'private-gh-app')
<livewire:project.new.github-private-repository :type="$type" />
@elseif ($type === 'private-deploy-key')
<livewire:project.new.github-private-repository-deploy-key :type="$type" />
@endif
<div x-data="{ activeTab: 'choose' }">
<div class="flex flex-col w-64 gap-2 mb-10">
<x-inputs.button @click.prevent="activeTab = 'public-repo'">Public Repository</x-inputs.button>
<x-inputs.button @click.prevent="activeTab = 'github-private-repo'">Private Repository (with GitHub App)
</x-inputs.button>
<x-inputs.button @click.prevent="activeTab = 'github-private-repo-deploy-key'">Private Repository (with
Deploy Key)
</x-inputs.button>
@if ($type === 'project')
<livewire:project.new.empty-project />
@endif
</div>
<div x-cloak x-show="activeTab === 'public-repo'">
<livewire:project.new.public-git-repository :type="$type" />
</div>
<div x-cloak x-show="activeTab === 'github-private-repo'">
<livewire:project.new.github-private-repository :type="$type" />
</div>
<div x-cloak x-show="activeTab === 'github-private-repo-deploy-key'">
<livewire:project.new.github-private-repository-deploy-key :type="$type" />
</div>
</div>
</x-layout>