Skip to content

Instantly share code, notes, and snippets.

@assertchris
Created December 24, 2019 19:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save assertchris/d570feb4a73c30fa1d17082a4e1dbf49 to your computer and use it in GitHub Desktop.
Save assertchris/d570feb4a73c30fa1d17082a4e1dbf49 to your computer and use it in GitHub Desktop.
Livewire state hoops
<?php
namespace App\Http\Livewire;
class CacheProxy
{
private $key;
public function __construct($key)
{
$this->key = $key;
}
public function get($default = null, $assoc = false)
{
return json_decode(cache()->get($this->key, json_encode($default)), $assoc);
}
public function getRaw($default = null)
{
return cache()->get($this->key, $default);
}
public function set($value)
{
cache()->forever($this->key, json_encode($value));
}
public function setRaw($value)
{
cache()->forever($this->key, $value);
}
public function forget()
{
cache()->forget($this->key);
}
}
<?php
namespace App\Http\Livewire;
use Illuminate\Support\Str;
use Livewire\Component as BaseComponent;
class Component extends BaseComponent
{
public function uuid()
{
return (string) Str::uuid();
}
public function cache($key)
{
static $cacheProxies;
if (!$cacheProxies) {
$cacheProxies = [];
}
if (!isset($cacheProxies[$key])) {
$cacheProxies[$key] = new CacheProxy($key);
}
return $cacheProxies[$key];
}
}
<div class="flex flex-col w-full h-screen">
<h1 class="text-xl">Select repositories</h1>
@guest
<a href="{{ route('users.redirect-to-github', ['elevated' => 1]) }}" class="text-indigo-500 underline text-sm">Log in with Github</a>
<a href="{{ route('users.redirect-to-bitbucket', ['elevated' => 1]) }}" class="text-indigo-500 underline text-sm">Log in with BitBucket</a>
@endguest
@auth
<div class="my-2">
<button wire:click="sync" class="bg-gray-900 text-white px-2 py-1">sync</button>
@unless(auth()->user() && auth()->user()->github_elevated_token)
<a href="{{ route('users.redirect-to-github', ['elevated' => 1]) }}" class="text-indigo-500 underline ml-2 text-sm">Connect Github</a>
@endunless
@unless(auth()->user() && auth()->user()->bitbucket_elevated_token)
<a href="{{ route('users.redirect-to-bitbucket', ['elevated' => 1]) }}" class="text-indigo-500 underline ml-2 text-sm">Connect BitBucket</a>
@endunless
</div>
@forelse($available as $repository)
<label class="flex flex-row items-center justify-start w-full px-2 py-1">
<input
type="checkbox"
value="{{ json_encode($repository) }}"
wire:change="$emit('onSelectRepository', $event.target.value, $event.target.checked)"
class="mr-2"
@if(count($selected) > 0 and in_array($repository->name, array_keys($selected)) and $selected[$repository->name]))
checked
@endif
/> {{ $repository->name }} ({{ $repository->source }})
</label>
@empty
No repositories.
@endforelse
@endauth
<div class="my-2">
<button wire:click="$emit('onPrevious')" class="bg-gray-900 text-white px-2 py-1">previous</button>
@if($selected and count(array_keys($selected)) > 0)
<button wire:click="$emit('onNext')" class="bg-gray-900 text-white px-2 py-1">next</button>
@endif
</div>
</div>
<div class="flex flex-col w-full h-screen">
<h1 class="text-xl">Select type</h1>
@foreach($available as $type)
<label class="flex flex-row items-center justify-start w-full px-2 py-1">
<input
type="radio"
name="wizard-type"
value="{{ json_encode($type) }}"
wire:change="$emit('onSelectType', $event.target.value, $event.target.checked)"
class="mr-2"
@if($selected and $selected->id == $type->id)
checked
@endif
/> {{ $type->name }}
</label>
@endforeach
<div class="my-2">
@if($selected)
<button wire:click="$emit('onNext')" class="bg-gray-900 text-white px-2 py-1">next</button>
@endif
</div>
</div>
<?php
namespace App\Http\Livewire;
class SelectRepositories extends Component
{
public $availableCacheUuid;
public $selectedCacheUuid;
public function mount($selected = [])
{
$this->availableCacheUuid = session()->get('SelectRepositories.availableCacheUuid', $this->uuid());
$this->selectedCacheUuid = session()->get('SelectRepositories.selectedCacheUuid', $this->uuid());
// remember these if we just created them...
session()->put('SelectRepositories.availableCacheUuid', $this->availableCacheUuid);
session()->put('SelectRepositories.selectedCacheUuid', $this->selectedCacheUuid);
$this->cache($this->selectedCacheUuid)->set($selected);
}
public function sync()
{
$available = array_merge(
$this->syncGithub(),
$this->syncBitbucket(),
);
$this->cache($this->availableCacheUuid)->set($available);
}
private function syncGithub()
{
$user = auth()->user();
if (!$user || !$user->github_elevated_token) {
return [];
}
sleep(1);
return [
(object) [
'name' => 'assertchris/attempt-promise',
'source' => 'github',
],
];
}
private function syncBitbucket()
{
$user = auth()->user();
if (!$user || !$user->bitbucket_elevated_token) {
return [];
}
sleep(1);
return [
(object) [
'name' => 'assertchris/solr-full-text-search',
'source' => 'bitbucket',
],
];
}
public function render()
{
$available = $this->cache($this->availableCacheUuid)->get([]);
$selected = $this->cache($this->selectedCacheUuid)->get([], true);
return view('livewire.select-repositories', compact('available', 'selected'));
}
}
<?php
namespace App\Http\Livewire;
class SelectType extends Component
{
protected $available;
protected $selected;
public function mount($selected = null)
{
$this->available = [
(object) [
'id' => 'files-for-sale',
'name' => 'files for sale',
],
(object) [
'id' => 'files-for-email',
'name' => 'files for email',
],
(object) [
'id' => 'repositories-for-sale',
'name' => 'repositories for sale',
],
(object) [
'id' => 'repositories-for-email',
'name' => 'repositories for email',
],
];
$this->selected = $selected;
}
public function render()
{
$available = $this->available;
$selected = $this->selected;
return view('livewire.select-type', compact('available', 'selected'));
}
}
<div class="flex flex-row w-full h-screen">
<div class="flex flex-col w-2/3 pr-8">
@if($this->show === 'select-type')
@livewire('select-type', $type, key('select-type-' . json_encode($type)))
@endif
@if($this->show === 'select-repositories')
@livewire('select-repositories', $repositories, key('select-repositories-' . json_encode($repositories)))
@endif
@if($this->show === 'upload-files')
@livewire('upload-files')
@endif
@if($this->show === 'select-provider')
@livewire('select-provider')
@endif
@if($this->show === 'show-confirmation')
@livewire('show-confirmation')
@endif
</div>
<div class="flex flex-col items-start justify-start w-1/3 text-gray-500">
<h1 class="text-lg">Debug info</h1>
@if($type)
<div>
Type:
<div>
{{ json_encode($type) }}
</div>
</div>
@endif
@if(count(array_filter($repositories)) > 0)
<div>
Repositories:
@foreach($repositories as $repository)
<div>
{{ json_encode($repository) }}
</div>
@endforeach
</div>
@endif
<button wire:click="flush" class="bg-gray-900 text-white px-2 py-1 mt-2">Flush</button>
</div>
</div>
@push('scripts')
<script type="text/javascript">
var queue = []
var current = undefined
function stop(e) {
e.preventDefault()
e.stopPropagation()
}
function show(element) {
element.classList.remove('hidden')
element.classList.add('flex')
}
function hide(element) {
element.classList.remove('flex')
element.classList.add('hidden')
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
var reader = new FileReader()
reader.onload = function() {
resolve(reader.result)
}
reader.onerror = function(error) {
reject(error)
}
reader.readAsDataURL(file)
})
}
function uploadFile(file) {
queue.push({
name: file.name,
isBusy: false,
isComplete: false,
error: undefined,
file: file
})
updateStatus()
}
function updateStatus() {
// DEBUG
// console.log(queue)
document.querySelector('[data-uploader-status]').innerHTML = `
${queue.map(upload => `
<div class="flex flex-col w-full md:w-1/2 pr-2 pb-2">
<div class="flex w-full">${upload.name}</div>
<div class="flex w-full">
${upload.isBusy ? 'uploading' : ''}
${upload.isComplete ? 'uploaded' : ''}
${upload.error ? error.message : ''}
</div>
</div>
`).join('\n')}
`
}
setInterval(function() {
if (current) {
// DEBUG
// console.log('still busy')
return
}
var next = queue.find(next => !next.isBusy && !next.isComplete && !next.error)
if (!next) {
// DEBUG
// console.log('empty queue', queue)
return
}
// DEBUG
console.log('starting next', next)
current = next
current.isBusy = true
updateStatus()
fileToBase64(current.file)
.then(function(data) {
window.livewire.emit('onUploadFile', current.name, current.type, current.size, data)
})
.catch(function(error) {
console.log('file had an error', current.name, error)
current.isBusy = false
current.error = error
current = undefined
updateStatus()
})
}, 100)
document.addEventListener('DOMContentLoaded', function () {
console.log('attaching uploader events')
document.addEventListener('dragenter', function(e) {
if (e.target.matches('[data-uploader-drop]')) {
document.querySelectorAll('[data-uploader-overlay]').forEach(element => show(element))
}
stop(e)
}, false)
document.addEventListener('dragleave', function(e) {
if (e.target.matches('[data-uploader-drop]')) {
document.querySelectorAll('[data-uploader-overlay]').forEach(element => hide(element))
}
stop(e)
}, false)
document.addEventListener('dragover', function(e) {
// if we don't stop this, we don't get a drop event...
stop(e)
}, false)
document.addEventListener('drop', function(e) {
console.log('drop', e)
if (e.target.matches('[data-uploader-drop]')) {
document.querySelectorAll('[data-uploader-overlay]').forEach(element => hide(element))
var files = Array.from(e.dataTransfer.files)
files.forEach(file => uploadFile(file))
}
stop(e)
}, false)
window.livewire.on('onUploadedFile', function(name, uuid) {
console.log('file was uploaded', name, uuid)
current.isBusy = false
current.isComplete = true
current.uuid = uuid
current = undefined
updateStatus()
})
})
</script>
@endpush
<?php
namespace App\Http\Livewire;
class ShowWizard extends Component
{
protected $listeners = [
'onSelectType' => 'onSelectType',
'onSelectRepository' => 'onSelectRepository',
'onPrevious' => 'onPrevious',
'onNext' => 'onNext',
];
public $show;
public $typeCacheUuid;
public $repositoriesCacheUuid;
public function mount()
{
$this->show = session()->get('ShowWizard.step', 'select-type');
$this->typeCacheUuid = session()->get('ShowWizard.typeCacheUuid', $this->uuid());
$this->repositoriesCacheUuid = session()->get('ShowWizard.repositoriesCacheUuid', $this->uuid());
// remember these if we just created them...
session()->put('ShowWizard.typeCacheUuid', $this->typeCacheUuid);
session()->put('ShowWizard.repositoriesCacheUuid', $this->repositoriesCacheUuid);
}
public function onSelectType($json, $checked)
{
$data = json_decode($json);
$this->cache($this->typeCacheUuid)->set($data);
}
public function onSelectRepository($json, $checked)
{
$data = json_decode($json);
$repositories = $this->cache($this->repositoriesCacheUuid)->get([], true);
if ($checked) {
$repositories[$data->name] = $data;
} else {
$repositories[$data->name] = null;
}
$this->cache($this->repositoriesCacheUuid)->set($repositories);
}
public function onPrevious()
{
$type = $this->cache($this->typeCacheUuid)->get();
if (in_array($this->show, ['select-repositories', 'upload-files'])) {
$this->show = 'select-type';
} else if (in_array($this->show, ['select-provider']) and in_array($type->id, ['repositories-for-sale', 'repositories-for-email'])) {
$this->show = 'select-repositories';
} else if (in_array($this->show, ['select-provider']) and in_array($type->id, ['files-for-sale', 'files-for-email'])) {
$this->show = 'upload-files';
} else if (in_array($this->show, ['show-confirmation'])) {
$this->show = 'select-provider';
}
session()->put('ShowWizard.step', $this->show);
}
public function onNext()
{
$type = $this->cache($this->typeCacheUuid)->get();
if (in_array($this->show, ['select-type']) and in_array($type->id, ['repositories-for-sale', 'repositories-for-email'])) {
$this->show = 'select-repositories';
} else if (in_array($this->show, ['select-type']) and in_array($type->id, ['files-for-sale', 'files-for-email'])) {
$this->show = 'upload-files';
} else if (in_array($this->show, ['select-repositories'])) {
$this->show = 'select-provider';
} else if (in_array($this->show, ['select-provider'])) {
$this->show = 'show-confirmation';
}
session()->put('ShowWizard.step', $this->show);
}
public function flush()
{
cache()->flush();
session()->flush();
$this->redirect(route('show-wizard'));
}
public function render()
{
$type = $this->cache($this->typeCacheUuid)->get();
$repositories = $this->cache($this->repositoriesCacheUuid)->get([], true);
return view('livewire.show-wizard', compact('type', 'repositories'));
}
}
<div data-uploader-drop class="flex flex-col w-full h-screen">
<h1 class="text-xl">Upload files</h1>
<div data-uploader-status class="flex flex-row flex-wrap w-full">&nbsp;</div>
<div class="my-2">
<button wire:click="$emit('onPrevious')" class="bg-gray-900 text-white px-2 py-1">Previous</button>
@if(count($uploaded) > 0)
<button wire:click="$emit('onNext')" class="bg-gray-900 text-white px-2 py-1">Next</button>
@endif
</div>
<div data-uploader-overlay class="hidden absolute top-0 right-0 bottom-0 left-0 bg-white opacity-75 pointer-events-none">&nbsp;</div>
<div data-uploader-overlay class="hidden items-center justify-center absolute top-0 right-0 bottom-0 left-0 pointer-events-none">
<div class="flex text-3xl text-black text-semibold pointer-events-none">Drop files to upload</div>
</div>
</div>
<?php
namespace App\Http\Livewire;
class UploadFiles extends Component
{
protected $listeners = [
'onUploadFile' => 'onUploadFile',
];
public $uploadedCacheUuid;
public function mount($uploaded = [])
{
$this->uploadedCacheUuid = session()->get('UploadFiles.uploadedCacheUuid', $this->uuid());
// remember these if we just created them...
session()->put('UploadFiles.uploadedCacheUuid', $this->uploadedCacheUuid);
$this->cache($this->uploadedCacheUuid)->set($uploaded);
}
public function onUploadFile($name, $type, $size, $data)
{
$uuid = $this->uuid();
sleep(5);
$this->emit('onUploadedFile', $name, $uuid);
}
public function render()
{
$uploaded = $this->cache($this->uploadedCacheUuid)->get([], true);
return view('livewire.upload-files', compact('uploaded'));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment