start reimplementing in Vue

This commit is contained in:
joshuaboud 2022-05-12 17:37:14 -03:00
parent 7e4d1a6b9a
commit 2f6ae732e5
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
28 changed files with 2945 additions and 35 deletions

167
makefile
View File

@ -1,46 +1,143 @@
# Cockpit Navigator - A File System Browser for Cockpit.
# Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
# This file is part of Cockpit Navigator.
# Cockpit Navigator is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Cockpit Navigator is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Automatic Houston Plugin Makefile
# Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
#
# Automatic Houston Plugin Makefile is free software: you can redistribute it and/or modify it under the terms
# of the GNU General Public License as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# Automatic Houston Plugin Makefile is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
#
# You should have received a copy of the GNU General Public License along with Automatic Houston Plugin Makefile.
# If not, see <https://www.gnu.org/licenses/>.
EL7_DIST=.el7
# PLUGIN_SRCS is space-delimited list of subdirectories containg a plugin project.
# You can leave it empty for automatic detection based on directories containing a package.json file.
PLUGIN_SRCS=
default:
# For installing to a remote machine for testing with `make install-remote`
REMOTE_TEST_HOST=osd1
REMOTE_TEST_USER=root
# Restarts cockpit after install
RESTART_COCKPIT?=0
# When set to 1, JS is not minified
DEBUG?=0
# Run yarn upgrade or npm update for each project before build
AUTO_UPGRADE_DEPS?=1
# USAGE
# installation:
# $ make
# # make install
# testing:
# $ make
# $ make install-local
# or
# $ make install-remote
################################
# Do not edit anything below
define greentext
'\033[1;32m$(1)\033[0m'
endef
define cyantext
'\033[1;96m$(1)\033[0m'
endef
ifeq ($(DEBUG),1)
BUILD_FLAGS=-- --minify false
endif
ifndef PLUGIN_SRCS
PLUGIN_SRCS:=$(patsubst %/package.json,%,$(wildcard */package.json))
endif
OUTPUTS:=$(addsuffix /dist/index.html, $(PLUGIN_SRCS))
NPM_PREFIX:=$(shell command -v yarn > /dev/null 2>&1 && echo 'yarn --cwd' || echo 'npm --prefix')
NPM_UPDATE:=$(shell command -v yarn > /dev/null 2>&1 && echo 'yarn upgrade --cwd' || echo 'npm update --prefix')
VERSION_FILES:=$(addsuffix /src/version.js, $(PLUGIN_SRCS))
OS_PACKAGE_RELEASE?=built_from_source
default: $(VERSION_FILES) $(OUTPUTS)
all: default
install:
mkdir -p $(DESTDIR)/usr/share/cockpit/
cp -rpf navigator $(DESTDIR)/usr/share/cockpit
ifeq ($(DIST),$(EL7_DIST))
sed -i "s/pf-c-button/btn/g;s/pf-m-primary/btn-primary/g;s/pf-m-secondary/btn-default/g;s/pf-m-danger/btn-danger/g" $(DESTDIR)/usr/share/cockpit/navigator/index.html
sed -i "s/pf-c-button/btn/g;s/pf-m-primary/btn-primary/g;s/pf-m-secondary/btn-default/g;s/pf-m-danger/btn-danger/g" $(DESTDIR)/usr/share/cockpit/navigator/components/ModalPrompt.js
.PHONY: default all install clean help install-local install-remote install
$(VERSION_FILES): ./manifest.json
echo 'export const pluginVersion = "$(shell jq -r '.version' ./manifest.json)-$(shell jq -r '.buildVersion' ./manifest.json)$(OS_PACKAGE_RELEASE)";' > $@
# build outputs
.SECONDEXPANSION:
$(OUTPUTS): %/dist/index.html: $$(shell find $$*/src -type f) $$(shell find $$*/public -type f) $$(shell find $$* -name 'yarn.lock' -o -name 'package.json' -not -path '*node_modules*') $$*/*.html $$*/*.js
@echo -e $(call cyantext,Building $*)
$(NPM_PREFIX) $* install
ifeq ($(AUTO_UPGRADE_DEPS),1)
$(NPM_UPDATE) $*
endif
$(NPM_PREFIX) $* run build $(BUILD_FLAGS)
@echo -e $(call greentext,Done building $*)
@echo
# system install, requires `systemctl restart cockpit.socket`
# runs plugin-install-* for each plugin
.SECONDEXPANSION:
install install-local install-remote: default $$(addprefix plugin-$$@-, $$(PLUGIN_SRCS))
ifeq ($(RESTART_COCKPIT), 1)
ifndef DESTDIR
$(SSH) systemctl stop cockpit.socket
$(SSH) systemctl start cockpit.socket
endif
ifneq ($(NAV_VERS),)
echo "export let NAVIGATOR_VERSION = \"$(NAV_VERS)\";" > $(DESTDIR)/usr/share/cockpit/navigator/version.js
endif
uninstall:
rm -rf $(DESTDIR)/usr/share/cockpit/navigator
install-remote : SSH=ssh $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST)
install-local:
mkdir -p $(HOME)/.local/share/cockpit
cp -rpf navigator $(HOME)/.local/share/cockpit
find $(HOME)/.local/share/cockpit/navigator -name '*.js' -exec sed -i "s#\"/usr/share/\(cockpit/navigator/scripts/.*\)\"#\"$(HOME)/.local/share/\1\"#g" {} \;
ifneq ($(NAV_VERS),)
echo "export let NAVIGATOR_VERSION = \"$(NAV_VERS)\";" > $(HOME)/.local/share/cockpit/navigator/version.js
endif
plugin-install-% plugin-install-local-% plugin-install-remote-%:
@echo -e $(call cyantext,Installing $*)
@echo Creating install directory
$(SSH) mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/$*$(INSTALL_SUFFIX)
@echo
@echo Copying files
@test -z "$(SSH)" && \
cp -af $*/dist/* $(DESTDIR)$(INSTALL_PREFIX)/$*$(INSTALL_SUFFIX) || \
rsync -avh $*/dist/* $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST):$(DESTDIR)$(INSTALL_PREFIX)/$*$(INSTALL_SUFFIX)
@echo -e $(call greentext,Done installing $*)
@echo
uninstall-local:
rm -rf $(HOME)/.local/share/cockpit/navigator
plugin-install-% : INSTALL_PREFIX?=/usr/share/cockpit
plugin-install-local-% : INSTALL_PREFIX=$(HOME)/.local/share/cockpit
plugin-install-local-% : INSTALL_SUFFIX=-test
plugin-install-remote-% : INSTALL_PREFIX=$(REMOTE_TEST_HOME)/.local/share/cockpit
plugin-install-remote-% : INSTALL_SUFFIX=-test
plugin-install-remote-% : SSH=ssh $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST)
plugin-install-remote-% : REMOTE_TEST_HOME=$(shell ssh $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST) 'echo $$HOME')
clean: FORCE
rm $(dir $(OUTPUTS)) -rf
help:
@echo 'make usage'
@echo
@echo 'building:'
@echo ' make'
@echo
@echo 'installation:'
@echo ' make install [RESTART_COCKPIT=1]'
@echo
@echo 'testing:'
@echo ' make install-local [RESTART_COCKPIT=1]'
@echo 'or'
@echo ' make install-remote [RESTART_COCKPIT=1]'
@echo
@echo 'build cleanup:'
@echo ' make clean'
FORCE:

26
navigator-vue/.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Generated files
version.js

24
navigator-vue/README.md Normal file
View File

@ -0,0 +1,24 @@
# vue
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

19
navigator-vue/index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="../base1/cockpit.js"></script>
<script src="../manifests.js"></script>
<script src="../*/po.js"></script>
<title>Users and Groups</title>
</head>
<body class="h-full overflow-hidden">
<div id="app" class="h-full"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,30 @@
{
"name": "navigator",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@45drives/regex": "^0.1.0",
"vue": "^3.2.25"
},
"devDependencies": {
"@45drives/cockpit-css": "^0.1.5",
"@45drives/cockpit-helpers": "^0.1.8",
"@45drives/cockpit-syntaxes": "^0.1.4",
"@fontsource/red-hat-text": "^4.5.4",
"@headlessui/vue": "^1.5.0",
"@heroicons/vue": "^1.0.6",
"@tailwindcss/forms": "^0.5.0",
"@vitejs/plugin-vue": "^2.2.0",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.8",
"source-sans-pro": "^3.6.0",
"tailwindcss": "^3.0.23",
"vite": "^2.8.0",
"vue-router": "^4.0.15"
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
plugins: {
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,11 @@
{
"version": 2.0,
"menu": {
"identities": {
"label": "Navigator",
"path": "index.html",
"order": 116
}
}
}

55
navigator-vue/src/App.vue Normal file
View File

@ -0,0 +1,55 @@
<template>
<div class="text-default bg-default h-full flex flex-col items-stretch">
<router-view v-if="providesValid" @updateFooterText="text => routerViewFooterText = text" />
<div class="flex flex-row items-center px-4 py-2">
<div class="text-sm" v-html="routerViewFooterText"></div>
<div class="grow" />
<SettingsMenu />
</div>
</div>
<Notifications :notificationFIFO="notificationFIFO" ref="notifications" />
</template>
<script setup>
import { ref, reactive, provide, onBeforeMount } from "vue";
import SettingsMenu from "./components/SettingsMenu.vue";
import Notifications from './components/Notifications.vue';
import { FIFO } from '@45drives/cockpit-helpers';
import { settingsInjectionKey, notificationsInjectionKey, pathHistoryInjectionKey } from "./keys";
const props = defineProps({ notificationFIFO: FIFO });
const providesValid = ref(false);
const notifications = ref();
provide(notificationsInjectionKey, notifications);
const settings = reactive({});
provide(settingsInjectionKey, settings);
const pathHistory = reactive({
stack: [],
index: 0,
back: () => {
pathHistory.index = Math.max(0, pathHistory.index - 1);
return pathHistory.stack[pathHistory.index];
},
forward: () => {
pathHistory.index = Math.min(pathHistory.index + 1, pathHistory.stack.length - 1);
return pathHistory.stack[pathHistory.index];
},
push: (path) => {
pathHistory.stack = pathHistory.stack.slice(0, pathHistory.index + 1);
pathHistory.stack.push(path);
pathHistory.index = Math.min(pathHistory.index + 1, pathHistory.stack.length - 1);
},
current: () => pathHistory.stack[pathHistory.index],
backAllowed: () => pathHistory.index > 0,
forwardAllowed: () => pathHistory.index < pathHistory.stack.length - 1,
});
provide(pathHistoryInjectionKey, pathHistory);
providesValid.value = true;
const routerViewFooterText = ref("");
</script>

View File

@ -0,0 +1,97 @@
<template>
<tr v-if="listView" @dblclick="doubleClickCallback" class="hover:!bg-red-600/10">
<td class="w-6 !py-0 !px-1">
<div class="relative">
<component :is="icon" class="size-icon icon-default" />
<LinkIcon v-if="entry.type === 'link'" class="w-2 h-2 absolute right-0 bottom-0 text-default" />
</div>
</td>
<td class="!pl-1">
{{ entry.name }}
<div v-if="entry.type === 'link'" class="inline-flex gap-1 items-center">
<div class="inline relative">
<ArrowNarrowRightIcon class="text-default size-icon-sm inline" />
<XIcon v-if="entry.target?.broken" class="icon-danger size-icon-sm absolute inset-x-0 bottom-0" />
</div>
<div>{{ entry.target?.rawPath ?? '' }}</div>
</div>
</td>
<td v-if="settings?.directoryView?.cols?.mode" class="font-mono">{{ entry.modeStr }}</td>
<td v-if="settings?.directoryView?.cols?.owner">{{ entry.owner }}</td>
<td v-if="settings?.directoryView?.cols?.group">{{ entry.group }}</td>
<td v-if="settings?.directoryView?.cols?.size">{{ entry.sizeHuman }}</td>
<td v-if="settings?.directoryView?.cols?.ctime">{{ entry.ctime?.toLocaleString() ?? '-' }}</td>
<td v-if="settings?.directoryView?.cols?.mtime">{{ entry.mtime?.toLocaleString() ?? '-' }}</td>
<td v-if="settings?.directoryView?.cols?.atime">{{ entry.atime?.toLocaleString() ?? '-' }}</td>
</tr>
<div v-else @dblclick="doubleClickCallback" class="flex flex-col gap-content items-center">
<div class="relative">
<component
:is="icon"
:class="[settings.directoryView?.view === 'list' ? 'size-icon' : 'size-icon-xl', 'icon-default']"
/>
<LinkIcon
v-if="entry.type === 'link'"
class="size-icon-sm absolute right-0 bottom-0 text-gray-100 dark:text-gray-900"
/>
</div>
<div>{{ entry.name }}</div>
</div>
</template>
<script>
import { ref, inject, watch } from 'vue';
import { DocumentIcon, FolderIcon, LinkIcon, DocumentRemoveIcon, ArrowNarrowRightIcon, XIcon } from '@heroicons/vue/solid';
import { settingsInjectionKey } from '../keys';
export default {
props: {
entry: Object,
listView: Boolean,
},
setup(props, { emit }) {
const settings = inject(settingsInjectionKey);
const icon = ref(FolderIcon);
const directoryLike = ref(false);
const brokenLink = ref(false);
const doubleClickCallback = () => {
if (directoryLike.value) {
emit('cd', props.entry.path);
} else {
emit('edit', props.entry.path);
}
}
watch(props.entry, () => {
if (props.entry.type === 'directory' || (props.entry.type === 'link' && props.entry.target?.type === 'directory')) {
icon.value = FolderIcon;
directoryLike.value = true;
} else {
icon.value = DocumentIcon;
directoryLike.value = false;
}
}, { immediate: true });
return {
settings,
icon,
directoryLike,
brokenLink,
doubleClickCallback,
}
},
components: {
DocumentIcon,
FolderIcon,
LinkIcon,
DocumentRemoveIcon,
ArrowNarrowRightIcon,
XIcon,
},
emits: [
'cd',
'edit',
]
}
</script>

View File

@ -0,0 +1,363 @@
<template>
<Table
v-if="settings.directoryView?.view === 'list'"
emptyText="No entries."
noHeader
stickyHeaders
noShrink
noShrinkHeight="h-full"
class="rounded-lg"
>
<template #thead>
<tr>
<th class="w-6 !p-0">
<div class="flex items-center justify-center">
<LoadingSpinner v-if="processing" class="size-icon" />
</div>
</th>
<th class="!pl-1">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Name</div>
<SortCallbackButton
initialFuncIsMine
v-model="sortCallback"
:compareFunc="sortCallbacks.name"
/>
</div>
</th>
<th v-if="settings?.directoryView?.cols?.mode">Mode</th>
<th v-if="settings?.directoryView?.cols?.owner">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Owner</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.owner" />
</div>
</th>
<th v-if="settings?.directoryView?.cols?.group">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Group</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.group" />
</div>
</th>
<th v-if="settings?.directoryView?.cols?.size">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Size</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.size" />
</div>
</th>
<th v-if="settings?.directoryView?.cols?.ctime">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Created</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.ctime" />
</div>
</th>
<th v-if="settings?.directoryView?.cols?.mtime">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Modified</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.mtime" />
</div>
</th>
<th v-if="settings?.directoryView?.cols?.atime">
<div class="flex flex-row flex-nowrap gap-2">
<div class="grow">Accessed</div>
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.atime" />
</div>
</th>
</tr>
</template>
<template #tbody>
<DirectoryEntry
v-for="entry, index in entries"
:hidden="!settings.directoryView.showHidden && /^\./.test(entry.name)"
:key="entry.path"
:entry="entry"
listView
@cd="(...args) => $emit('cd', ...args)"
@edit="(...args) => $emit('edit', ...args)"
@sortEntries="sortEntries"
@updateStats="emitStats"
/>
</template>
</Table>
<div v-else class="flex flex-wrap gap-well p-well bg-well h-full overflow-y-auto">
<DirectoryEntry
v-for="entry, index in entries"
:hidden="!settings.directoryView.showHidden && /^\./.test(entry.name)"
:key="entry.path"
:entry="entry"
@cd="(...args) => $emit('cd', ...args)"
@edit="(...args) => $emit('edit', ...args)"
@sortEntries="sortEntries"
@updateStats="emitStats"
/>
</div>
</template>
<script>
import { ref, reactive, computed, inject, watch } from 'vue';
import DirectoryEntry from './DirectoryEntry.vue';
import { useSpawn, errorStringHTML, canonicalPath } from '@45drives/cockpit-helpers';
import Table from './Table.vue';
import { notificationsInjectionKey, settingsInjectionKey } from '../keys';
import LoadingSpinner from './LoadingSpinner.vue';
import SortCallbackButton from './SortCallbackButton.vue';
export default {
props: {
path: String,
},
setup(props, { emit }) {
const settings = inject(settingsInjectionKey);
const entries = ref([]);
const processing = ref(0);
const notifications = inject(notificationsInjectionKey);
const sortCallbacks = {
name: (a, b) => a.name.localeCompare(b.name),
owner: (a, b) => a.owner.localeCompare(b.owner),
group: (a, b) => a.group.localeCompare(b.group),
size: (a, b) => a.size - b.size,
ctime: (a, b) => a.ctime.getTime() - b.ctime.getTime(),
mtime: (a, b) => a.mtime.getTime() - b.mtime.getTime(),
atime: (a, b) => a.atime.getTime() - b.atime.getTime(),
}
const sortCallback = ref(() => 0);
const sortCallbackComputed = computed({
get() {
return (a, b) => {
if (settings.directoryView?.separateDirs) {
const checkA = a.type === 'link' ? (a.target?.type ?? null) : a.type;
const checkB = b.type === 'link' ? (b.target?.type ?? null) : b.type;
if (checkA === null || checkB === null)
return 0;
if (checkA === 'directory' && checkB !== 'directory')
return -1;
else if (checkA !== 'directory' && checkB === 'directory')
return 1;
}
return sortCallback.value(a, b);
}
},
set(value) {
sortCallback.value = value;
}
})
const getAsyncEntryStats = (cwd, entry, modeStr, path, linkTargetRaw) => {
const procs = [];
Object.assign(entry, {
permissions: {
owner: {
read: modeStr[1] !== '-',
write: modeStr[2] !== '-',
execute: modeStr[3] !== '-',
},
group: {
read: modeStr[4] !== '-',
write: modeStr[5] !== '-',
execute: modeStr[6] !== '-',
},
other: {
read: modeStr[7] !== '-',
write: modeStr[8] !== '-',
execute: modeStr[9] !== '-',
},
acl: modeStr[10] === '+' ? {} : null,
}
});
switch (modeStr[0]) {
case 'd':
entry.type = 'directory';
break;
case '-':
entry.type = 'file';
break;
case 'p':
entry.type = 'pipe';
break;
case 'l':
entry.type = 'link';
if (linkTargetRaw) {
entry.target = {
rawPath: linkTargetRaw,
path: canonicalPath(linkTargetRaw.replace(/^(?!=\/)/, cwd + '/')),
};
processing.value++;
procs.push(useSpawn(['stat', '-c', '%A', entry.target.path]).promise()
.then(state => {
getAsyncEntryStats(cwd, entry.target, state.stdout.trim());
entry.target.broken = false;
})
.catch(() => {
entry.target.broken = true;
})
.finally(() => processing.value--)
);
}
break;
case 's':
entry.type = 'socket';
break;
case 'c':
entry.type = 'character';
break;
case 'b':
entry.type = 'block';
break;
default:
entry.type = 'unk';
break;
}
if (entry.permissions.acl && path) {
processing.value++;
procs.push(useSpawn(['getfacl', '--omit-header', '--no-effective', path], { superuser: 'try' }).promise()
.then(state => {
entry.permissions.acl = state.stdout
.split('\n')
.filter(line => line && !/^\s*(?:#|$)/.test(line))
.reduce((acl, line) => {
const match = line.match(/^([^:]*):([^:]+)?:(.*)$/).slice(1);
acl[match[0]] = acl[match[0]] ?? {};
acl[match[0]][match[1] ?? '*'] = {
r: match[2][0] !== '-',
w: match[2][1] !== '-',
x: match[2][2] !== '-',
}
return acl;
}, {});
})
.catch(state => {
console.error(`failed to get ACL for ${path}:`, errorString(state));
})
.finally(() => processing.value--)
);
}
if (path) {
processing.value++;
procs.push(useSpawn(['stat', '-c', '%W:%Y:%X', path], { superuser: 'try' }).promise() // birth:modification:access
.then(state => {
const [ctimeStr, mtimeStr, atimeStr] = state.stdout.trim().split(':');
Object.assign(entry, {
ctime: new Date(parseInt(ctimeStr) * 1000),
mtime: new Date(parseInt(mtimeStr) * 1000),
atime: new Date(parseInt(atimeStr) * 1000),
});
})
.catch(state =>
notifications.value.constructNotification(`Failed to get stats for ${path}`, errorStringHTML(state), 'error')
)
.finally(() => processing.value--)
);
}
return Promise.all(procs);
}
const getEntries = async () => {
processing.value++;
try {
const cwd = props.path;
let lsOutput;
try {
lsOutput = (await useSpawn(['ls', '-al', '--color=never', '--time-style=full-iso', '--quote-name', '--dereference-command-line-symlink-to-dir', cwd], { superuser: 'try' }).promise()).stdout
} catch (state) {
if (state.exit_code === 1)
lsOutput = state.stdout; // non-fatal ls error
else
throw new Error(state.stderr);
}
const procs = [];
entries.value = lsOutput
.split('\n')
.filter(line => !/^(?:\s*$|total)/.test(line)) // remove empty lines
.map(record => {
try {
if (cwd !== props.path)
return null;
const entry = reactive({});
const fields = record.match(/^([a-z-]+\+?)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+(?:,\s+\d+)?)\s+([^"]+)"([^"]+)"(?:\s+->\s+"([^"]+)")?/)?.slice(1);
if (!fields) {
console.error('regex failed to match on', record);
return null;
}
entry.name = fields[6];
if (entry.name === '.' || entry.name === '..')
return null;
entry.path = canonicalPath(cwd + `/${entry.name}`);
entry.modeStr = fields[0];
entry.hardlinkCount = parseInt(fields[1]);
entry.owner = fields[2];
entry.group = fields[3];
if (/,/.test(fields[4])) {
[entry.major, entry.minor] = fields[4].split(/,\s+/);
entry.size = null;
} else {
entry.size = parseInt(fields[4]);
entry.sizeHuman = cockpit.format_bytes(entry.size, 1000).replace(/(?<!B)$/, ' B');
entry.major = entry.minor = null;
}
procs.push(getAsyncEntryStats(cwd, entry, entry.modeStr, entry.path, fields[7]));
return entry;
} catch (error) {
notifications.value.constructNotification(`Error while gathering info for ${entry.path ?? record}`, errorStringHTML(error), 'error');
return null;
}
})
.filter(entry => entry !== null)
?? [];
Promise.all(procs).then(() => {
emitStats();
sortEntries();
})
} catch (error) {
entries.value = [];
notifications.value.constructNotification("Error getting directory entries", errorStringHTML(error), 'error');
} finally {
processing.value--;
}
}
const emitStats = () => {
emit('updateStats', entries.value.reduce((stats, entry) => {
if (entry.type === 'directory' || (entry.type === 'link' && entry.target?.type === 'directory'))
stats.dirs++;
else
stats.files++;
return stats;
}, { files: 0, dirs: 0 }));
}
const sortEntries = () => {
processing.value++;
entries.value.sort(sortCallbackComputed.value);
processing.value--;
}
watch(sortCallback, sortEntries);
watch(entries, sortEntries);
watch(() => settings.directoryView?.separateDirs, sortEntries);
watch(() => props.path, getEntries, { immediate: true });
return {
settings,
entries,
processing,
sortCallbacks,
sortCallback,
getEntries,
emitStats,
sortEntries,
}
},
components: {
DirectoryEntry,
Table,
LoadingSpinner,
SortCallbackButton,
},
emits: [
'cd',
'edit',
'updateStats',
]
}
</script>

View File

@ -0,0 +1,56 @@
<template>
<SwitchGroup as="div" :class="[labelRight ? 'flex-row-reverse' : 'flex-row', 'inline-flex items-center']">
<span :class="[labelRight ? 'grow' : '', 'flex flex-col']">
<SwitchLabel as="span" class="text-label" passive><slot /></SwitchLabel>
<SwitchDescription
as="span"
class="text-sm text-muted"
><slot name="description" /></SwitchDescription>
</span>
<div :class="[labelRight ? '' : 'grow', 'w-4']"></div> <!-- spacer -->
<Switch
:modelValue="modelValue"
@update:modelValue="newValue => { $emit('update:modelValue', newValue); $emit('change', newValue); $emit('input', newValue); }"
:class="[modelValue ? 'bg-45d' : 'bg-well', 'relative inline-flex flex-shrink-0 h-6 w-11 p-[2px] rounded-full cursor-pointer focus:outline-none focus:ring-0 shadow-inner']"
>
<span
aria-hidden="true"
:class="[modelValue ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-default shadow-md transform ring-0 transition-transform ease-in-out duration-200 top-px']"
/>
</Switch>
</SwitchGroup>
</template>
<script>
import { Switch, SwitchDescription, SwitchGroup, SwitchLabel } from '@headlessui/vue'
import { watch, ref } from 'vue';
export default {
props: {
modelValue: Boolean,
labelRight: Boolean,
},
// setup(props, { emit }) {
// const internalModel = ref(props.modelValue);
// const updateModelValue = () => emit('update:modelValue', internalModel.value);
// watch(() => props.modelValue, (newValue) => internalModel.value = newValue);
// return {
// internalModel,
// updateModelValue,
// };
// },
components: {
Switch,
SwitchDescription,
SwitchGroup,
SwitchLabel,
},
emits: [
'update:modelValue',
'change',
'input',
],
}
</script>

View File

@ -0,0 +1,37 @@
<!--
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit File Sharing.
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="aspect-square loading-spinner border-neutral-300 border-t-neutral-500 dark:border-neutral-500 dark:border-t-neutral-200 rounded-full"></div>
</template>
<style>
.loading-spinner {
border-width: 0.2rem;
animation: spin 2s linear infinite;
display: inline-block;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<TransitionRoot as="template" :show="showModal">
<Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto">
<div
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0 text-default"
>
<TransitionChild
as="template"
enter="ease-out duration-300"
enter-from="opacity-0"
enter-to="opacity-100"
leave="ease-in duration-200"
leave-from="opacity-100"
leave-to="opacity-0"
>
<DialogOverlay class="fixed inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity" />
</TransitionChild>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<TransitionChild
as="template"
enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div
:class="[autoWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'relative inline-flex flex-col items-stretch align-bottom overflow-hidden transform transition-all sm:my-8 sm:align-middle text-left card']"
>
<div class="block w-[512px]"></div>
<div class="card-header">
<slot name="header">
<h3 class="text-header">{{ headerText }}</h3>
</slot>
</div>
<div class="card-body flex flex-row items-center">
<div class="shrink-0">
<slot name="icon" />
</div>
<div class="grow">
<slot />
</div>
</div>
<div class="card-footer button-group-row w-full justify-end">
<button
v-if="!noCancel"
type="button"
class="btn btn-secondary"
@click="$emit('cancel')"
>{{ cancelText }}</button>
<button
type="button"
:class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
@click="$emit('apply')"
:disabled="disableContinue"
>{{ applyText }}</button>
</div>
</div>
</TransitionChild>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue';
export default {
props: {
showModal: Boolean,
noCancel: {
type: Boolean,
required: false,
default: false,
},
autoWidth: Boolean,
headerText: String,
cancelText: {
type: String,
required: false,
default: "Cancel",
},
applyText: {
type: String,
required: false,
default: "Apply",
},
applyDangerous: Boolean,
disableContinue: Boolean,
},
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
},
emits: [
'apply',
'cancel',
]
};
</script>

View File

@ -0,0 +1,219 @@
<template>
<!-- Global notification live region, render this permanently at the end of the document -->
<div
aria-live="assertive"
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20 h-screen overflow-y-auto"
>
<div class="w-full flex flex-col items-center sm:items-end space-y-content">
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<transition
v-for="notification in notificationList"
:key="notification.id"
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
class="transition-transform"
>
<div
v-if="notification.show"
class="max-w-sm w-full shadow-lg pointer-events-auto overflow-hidden bg-default text-default"
@mouseenter="notification.clearTimeouts?.()"
@mouseleave="notification.setTimeouts?.()"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0" aria-hidden="true">
<ExclamationCircleIcon
v-if="notification.level === 'error'"
class="icon-error size-icon-lg"
aria-hidden="true"
/>
<ExclamationCircleIcon
v-else-if="notification.level === 'warning'"
class="icon-warning size-icon-lg"
aria-hidden="true"
/>
<CheckCircleIcon
v-else-if="notification.level === 'success'"
class="icon-success size-icon-lg"
aria-hidden="true"
/>
<MinusCircleIcon
v-else-if="notification.level === 'denied'"
class="icon-error size-icon-lg"
aria-hidden="true"
/>
<InformationCircleIcon v-else class="icon-info size-icon-lg" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium">{{ notification.title }}</p>
<p class="mt-1 text-sm text-muted whitespace-pre-wrap" v-html="notification.body"></p>
<div v-if="notification.actions?.length" class="mt-3 flex space-x-7">
<button
v-for="action in notification.actions"
@click="action.callback"
class="rounded-md text-sm font-medium text-primary focus:outline-none focus:ring-0 focus:ring-offset-0"
>
{{ action.text }}
</button>
<button
@click="notification.show = false"
type="button"
class="rounded-md text-sm font-medium text-secondary focus:outline-none focus:ring-0 focus:ring-offset-0"
>Dismiss</button>
</div>
</div>
<div class="ml-4 flex-shrink-0 flex">
<button
@click="notification.show = false"
class="icon-default focus:outline-none focus:ring-0 focus:ring-offset-0"
>
<span class="sr-only">Close</span>
<XIcon class="size-icon" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { ref, watch, reactive, onUnmounted } from 'vue';
import { InformationCircleIcon, ExclamationCircleIcon, MinusCircleIcon, CheckCircleIcon } from '@heroicons/vue/outline';
import { XIcon } from '@heroicons/vue/solid';
import { FIFO, UniqueIDGenerator } from '@45drives/cockpit-helpers';
/** Notification passed to showNotification
*
* @typedef {Object} Notification
* @property {string} title - Header text
* @property {string} body - Notification content
* @property {string} level - 'info'|'warning'|'error'|'success'
* @property {number} timeout - time to display notification
* @property {function} addAction - add action to notification
*/
export default {
props: {
notificationFIFO: FIFO,
},
setup(props) {
const notificationList = ref([]);
const uniqueIDGenerator = new UniqueIDGenerator();
/** Construct new notification and show it
*
* @param {string} title - Header text
* @param {string} body - Notification content
* @param {string} [level='info'] - 'info'|'warning'|'error'|'success'
* @param {number} [timeout=10000] - time to display notification in milliseconds, or zero to display forever
*
* @returns {Notification} - Object to chain add actions to
*/
const constructNotification = (title, body, level = 'info', timeout = 10000) => {
const actions = [];
const obj = reactive({
show: true,
title,
body,
level,
timeout,
actions,
});
showNotificationObj(obj);
/** Add an action to the notification (chainable)
*
* @param {string} text button text for action
* @param {function} callback onclick callback for action
*/
obj.addAction = (text, callback) => {
obj.actions.push({
text,
callback: () => {
obj.show = false;
callback();
}
});
return obj;
};
return obj;
}
/** Display notification to screen
*
* @param {Notification} notificationObj notification to display
*
*/
const showNotificationObj = (notificationObj) => {
notificationObj.show = true;
notificationObj.id = uniqueIDGenerator.get();
notificationObj.setTimeouts = () => {
if (notificationObj.timeout > 0) {
notificationObj.timeout1 = setTimeout(
() => notificationObj.show = false,
notificationObj.timeout
);
notificationObj.timeout2 = setTimeout(
() => {
notificationList.value = notificationList.value.filter(obj => obj !== notificationObj);
uniqueIDGenerator.release(notificationObj.id);
},
notificationObj.timeout + 2000
);
}
}
notificationObj.clearTimeouts = () => {
if (notificationObj.timeout1 !== undefined)
clearTimeout(notificationObj.timeout1);
if (notificationObj.timeout2 !== undefined)
clearTimeout(notificationObj.timeout2);
}
notificationList.value = [notificationObj, ...notificationList.value];
if (notificationObj.level === 'error') {
console.error(notificationObj.title);
console.error(notificationObj.body);
}
notificationObj.setTimeouts();
}
watch(() => props.notificationFIFO.getLen(), (newLen, oldLen) => {
if (newLen > oldLen) {
try {
const notification = props.notificationFIFO.pop();
if (notification)
showNotificationObj(notification);
} catch (error) {
console.error(error);
constructNotification("System Error", "An error occured, check the system console (CTRL+SHIFT+J) for more information.", 'error');
}
}
});
let cleanupHandle = setInterval(() => {
notificationList.value = notificationList.value.filter(n => n.show);
}, 1000);
onUnmounted(() => clearInterval(cleanupHandle));
return {
notificationList,
constructNotification,
showNotificationObj,
}
},
components: {
InformationCircleIcon,
ExclamationCircleIcon,
MinusCircleIcon,
CheckCircleIcon,
XIcon,
},
}
</script>

View File

@ -0,0 +1,57 @@
<template>
<div class="flex items-center cursor-text h-10" @click="typing = true">
<input v-if="typing" v-model="pathInput" type="text" class="w-full input-textlike" @change="$emit('cd', canonicalPath(pathInput))" ref="inputRef" @focusout="typing = false" />
<div v-else class="inline-flex items-center gap-1">
<template v-for="segment, index in pathArr" :key="index">
<ChevronRightIcon v-if="index > 0" class="size-icon icon-default" />
<button
@click.prevent.stop="$emit('cd', canonicalPath(pathArr.slice(0, index + 1).join('/')))"
class="p-2 hover:bg-accent rounded-lg cursor-pointer"
>{{ segment }}</button>
</template>
</div>
</div>
</template>
<script>
import { ref, watch, nextTick } from 'vue';
import { canonicalPath } from "@45drives/cockpit-helpers";
import { ChevronRightIcon } from '@heroicons/vue/solid';
export default {
props: {
path: String,
},
setup(props) {
const pathArr = ref([]);
const typing = ref(false);
const pathInput = ref("");
const inputRef = ref();
watch(() => props.path, () => {
pathArr.value = ['/', ...canonicalPath(props.path).replace(/^\//, '').split('/')].filter(segment => segment);
pathInput.value = props.path;
typing.value = false;
}, { immediate: true });
watch(typing, () => {
if (typing.value)
nextTick(() => inputRef.value.focus());
});
return {
pathArr,
typing,
pathInput,
inputRef,
canonicalPath,
}
},
components: {
ChevronRightIcon,
},
emits: [
'cd',
]
}
</script>

View File

@ -0,0 +1,240 @@
<template>
<button @click="showMenu = true">
<AdjustmentsIcon class="size-icon icon-default" />
</button>
<ModalPopup
:showModal="showMenu"
autoWidth
headerText="Navigator Settings"
noCancel
applyText="Close"
@apply="showMenu = false"
>
<div class="flex flex-col gap-content items-start">
<div class="inline-flex flex-col gap-content">
<LabelledSwitch v-model="darkMode">Dark mode</LabelledSwitch>
<LabelledSwitch v-model="settings.directoryView.showHidden">Show hidden files</LabelledSwitch>
<LabelledSwitch
v-model="booleanAnalogs.directoryView.view.bool"
>{{ booleanAnalogs.directoryView.view.bool ? "List view" : "Grid view" }}</LabelledSwitch>
<LabelledSwitch v-model="settings.directoryView.separateDirs">Separate directories while sorting</LabelledSwitch>
</div>
<div v-if="booleanAnalogs.directoryView.view.bool" class="self-stretch">
<div>List view columns</div>
<div
class="flex justify-start text-sm rounded-lg divide-x divide-default border border-default shadow"
>
<div class="flex flex-col grow items-stretch">
<div class="font-semibold px-2">Visible</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.mode"
@click="settings.directoryView.cols.mode = !settings.directoryView.cols.mode"
>
Mode
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.owner"
@click="settings.directoryView.cols.owner = !settings.directoryView.cols.owner"
>
Owner
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.group"
@click="settings.directoryView.cols.group = !settings.directoryView.cols.group"
>
Group
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.size"
@click="settings.directoryView.cols.size = !settings.directoryView.cols.size"
>
Size
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.ctime"
@click="settings.directoryView.cols.ctime = !settings.directoryView.cols.ctime"
>
Created
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.mtime"
@click="settings.directoryView.cols.mtime = !settings.directoryView.cols.mtime"
>
Modified
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
v-if="settings.directoryView.cols.atime"
@click="settings.directoryView.cols.atime = !settings.directoryView.cols.atime"
>
Accessed
<ChevronRightIcon class="size-icon-sm icon-default" />
</div>
</div>
<div class="flex flex-col grow text-right items-stretch">
<div class="font-semibold px-2">Hidden</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.mode"
@click="settings.directoryView.cols.mode = !settings.directoryView.cols.mode"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Mode
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.owner"
@click="settings.directoryView.cols.owner = !settings.directoryView.cols.owner"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Owner
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.group"
@click="settings.directoryView.cols.group = !settings.directoryView.cols.group"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Group
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.size"
@click="settings.directoryView.cols.size = !settings.directoryView.cols.size"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Size
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.ctime"
@click="settings.directoryView.cols.ctime = !settings.directoryView.cols.ctime"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Created
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.mtime"
@click="settings.directoryView.cols.mtime = !settings.directoryView.cols.mtime"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Modified
</div>
<div
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
v-if="!settings.directoryView.cols.atime"
@click="settings.directoryView.cols.atime = !settings.directoryView.cols.atime"
>
<ChevronLeftIcon class="size-icon-sm icon-default" />Accessed
</div>
</div>
</div>
</div>
</div>
</ModalPopup>
</template>
<script>
import { ref, inject, watch, reactive } from 'vue';
import LabelledSwitch from './LabelledSwitch.vue';
import ModalPopup from './ModalPopup.vue';
import { AdjustmentsIcon, ChevronRightIcon, ChevronLeftIcon } from '@heroicons/vue/solid';
import { settingsInjectionKey } from '../keys';
const defaultSettings = {
directoryView: {
view: 'list',
showHidden: false,
separateDirs: true,
cols: {
mode: true,
owner: true,
group: true,
size: true,
ctime: true,
mtime: true,
atime: true,
},
},
}
export default {
setup() {
const showMenu = ref(false);
const settings = inject(settingsInjectionKey);
const settingsStorageKey = "houstonNavigatorSettingsKey";
const darkMode = inject('darkModeInjectionKey') ?? ref(false);
function getTheme() {
let prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
let theme = cockpit.localStorage.getItem("houston-color-theme");
if (theme === null)
return prefersDark;
return theme === "dark";
}
darkMode.value = getTheme();
const booleanAnalogs = reactive({
directoryView: {
view: { bool: false, trueValue: 'list', falseValue: 'grid' },
},
})
const storedSettings = JSON.parse(localStorage.getItem(settingsStorageKey)) ?? {};
Object.assign(settings, {
...defaultSettings,
...storedSettings,
directoryView: {
...defaultSettings.directoryView,
...storedSettings.directoryView,
cols: {
...defaultSettings.directoryView.cols,
...storedSettings.directoryView.cols,
}
},
});
console.log(settings);
watch(settings, () => {
localStorage.setItem(settingsStorageKey, JSON.stringify(settings));
booleanAnalogs.directoryView.view.bool = settings.directoryView.view === booleanAnalogs.directoryView.view.trueValue;
}, { immediate: true });
watch(booleanAnalogs, () => {
settings.directoryView.view =
booleanAnalogs.directoryView.view.bool
? booleanAnalogs.directoryView.view.trueValue
: booleanAnalogs.directoryView.view.falseValue;
}, { immediate: true });
watch(() => darkMode.value, (darkMode, oldDarkMode) => {
cockpit.localStorage.setItem("houston-color-theme", darkMode ? "dark" : "light");
if (darkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, { lazy: false, immediate: true });
cockpit.onvisibilitychange = () => darkMode.value = getTheme();
return {
showMenu,
settings,
darkMode,
booleanAnalogs,
}
},
components: {
ModalPopup,
LabelledSwitch,
AdjustmentsIcon,
ChevronRightIcon,
ChevronLeftIcon,
},
}
</script>

View File

@ -0,0 +1,69 @@
<template>
<button @click="updateModel()">
<SortDescendingIcon v-if="reverse" :class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']" />
<SortAscendingIcon v-else :class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']" />
</button>
</template>
<script>
import { nextTick, ref, watch } from 'vue';
import { SortAscendingIcon, SortDescendingIcon } from "@heroicons/vue/solid";
export default {
props: {
modelValue: Function,
compareFunc: Function,
initialFuncIsMine: {
type: Boolean,
required: false,
default: false,
},
startReversed: {
type: Boolean,
required: false,
default: false,
},
},
setup(props, { emit }) {
const reverse = ref(props.startReversed);
const funcIsMine = ref(props.initialFuncIsMine);
const iconComponent = ref(SortAscendingIcon);
const emitFunc = () => {
if (reverse.value)
emit('update:modelValue', (a, b) => props.compareFunc(b, a));
else
emit('update:modelValue', (a, b) => props.compareFunc(a, b));
// timeout to not overwrite change with self-triggered watch from emit
nextTick(() => funcIsMine.value = true);
}
if (props.initialFuncIsMine)
emitFunc();
const updateModel = () => {
if (funcIsMine.value)
reverse.value = !reverse.value;
emitFunc();
};
watch(() => props.modelValue, () => {
funcIsMine.value = false;
});
return {
reverse,
funcIsMine,
iconComponent,
updateModel,
}
},
components: {
SortAscendingIcon,
SortDescendingIcon
},
emits: [
'update:modelValue'
],
}
</script>

View File

@ -0,0 +1,98 @@
<template>
<div class="shadow border border-default h-full">
<div
v-if="!noHeader"
class="bg-accent py-3 px-4 lg:pl-8 lg:pr-6 text-sm font-semibold flex flex-row"
>
<div class="grow">
<slot name="header">{{ headerText }}</slot>
</div>
<div
:class="[noScroll ? '' : 'overflow-y-auto']"
:style="{ 'scrollbar-gutter': noScroll ? 'auto' : 'stable' }"
></div>
</div>
<div
:class="[noShrink ? noShrinkHeight : shrinkHeight, noScroll ? '' : 'overflow-y-scroll', 'flex flex-col overflow-x-auto']"
:style="{ 'scrollbar-gutter': noScroll ? 'auto' : 'stable' }"
>
<table class="min-w-full divide houston-table">
<thead :class="[stickyHeaders ? 'use-sticky' : '']">
<slot name="thead" />
</thead>
<tbody class="bg-default w-full">
<slot name="tbody">
<tr>
<td colspan="100%" class="text-center align-middle text-muted text-sm">{{ emptyText }}</td>
</tr>
</slot>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: {
headerText: {
type: String,
required: false,
default: "Table",
},
emptyText: {
type: String,
required: false,
default: "Nothing to show.",
},
noShrink: {
type: Boolean,
required: false,
default: false,
},
stickyHeaders: {
type: Boolean,
required: false,
default: false,
},
noShrinkHeight: {
type: String,
required: false,
default: 'h-80'
},
shrinkHeight: {
type: String,
required: false,
default: 'max-h-80'
},
noScroll: Boolean,
noHeader: Boolean,
}
}
</script>
<style>
@import "@45drives/cockpit-css/src/index.css";
table.houston-table thead.use-sticky tr th {
@apply sticky z-10 top-0;
}
table.houston-table th,
table.houston-table td {
@apply py-2 px-4 lg:pl-8 lg:pr-6 whitespace-nowrap text-sm;
}
table.houston-table th:not(.text-right):not(.text-center),
table.houston-table td:not(.text-right):not(.text-center) {
@apply text-left;
}
table.houston-table th {
@apply bg-accent font-semibold;
}
table.houston-table tr {
@apply even:bg-accent;
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<div class="flex flex-col h-full">
<div class="inline-block w-full align-middle h-full">
<div class="shadow md:rounded-[9px] border border-default h-full">
<div
v-if="!noHeader"
class="md:rounded-t-[8px] bg-accent py-3 px-4 lg:pl-8 lg:pr-6 text-sm font-semibold flex flex-row"
>
<div class="grow">
<slot name="header">
{{ headerText }}
</slot>
</div>
<div :class="[noScroll ? '' : 'overflow-y-auto']" :style="{'scrollbar-gutter': noScroll ? 'auto' : 'stable'}"></div>
</div>
<div
:class="[noShrink ? noShrinkHeight : shrinkHeight, noScroll ? '' : 'overflow-y-scroll', noHeader ? 'md:rounded-t-[8px]' : '', 'flex flex-col md:rounded-b-[8px] overflow-x-auto']"
:style="{'scrollbar-gutter': noScroll ? 'auto' : 'stable'}"
>
<table class="min-w-full divide houston-table">
<thead :class="[stickyHeaders ? 'use-sticky' : '']">
<slot name="thead" />
</thead>
<tbody class="bg-default w-full">
<slot name="tbody">
<tr>
<td colspan="100%" class="text-center align-middle text-muted text-sm">{{ emptyText }}</td>
</tr>
</slot>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
headerText: {
type: String,
required: false,
default: "Table",
},
emptyText: {
type: String,
required: false,
default: "Nothing to show.",
},
noShrink: {
type: Boolean,
required: false,
default: false,
},
stickyHeaders: {
type: Boolean,
required: false,
default: false,
},
noShrinkHeight: {
type: String,
required: false,
default: 'h-80'
},
shrinkHeight: {
type: String,
required: false,
default: 'max-h-80'
},
noScroll: Boolean,
noHeader: Boolean,
}
}
</script>
<style>
@import '@45drives/cockpit-css/src/index.css';
table.houston-table thead.use-sticky tr th {
@apply sticky z-10 top-0;
}
table.houston-table th,
table.houston-table td {
@apply py-2 px-4 lg:pl-8 lg:pr-6 whitespace-nowrap text-sm;
}
table.houston-table th:not(.text-right):not(.text-center),
table.houston-table td:not(.text-right):not(.text-center) {
@apply text-left;
}
table.houston-table th {
@apply bg-accent font-semibold;
}
table.houston-table tr {
@apply even:bg-accent;
}
</style>

16
navigator-vue/src/keys.js Normal file
View File

@ -0,0 +1,16 @@
/**
* Notifications component instance to add notifs
*/
export const notificationsInjectionKey = Symbol();
/**
* Settings object
*/
export const settingsInjectionKey = Symbol();
/**
* Path history object
*/
export const pathHistoryInjectionKey = Symbol();
/**
* localStorage lookup key for last path
*/
export const lastPathStorageKey = "houstonNavigatorLastPathKey";

41
navigator-vue/src/main.js Normal file
View File

@ -0,0 +1,41 @@
import { createApp, reactive } from 'vue';
import App from './App.vue';
import { FIFO } from '@45drives/cockpit-helpers';
import '@45drives/cockpit-css/src/index.css';
import router from './router';
const notificationFIFO = reactive(new FIFO());
const errorHandler = (error) => {
console.error(error);
const notificationObj = {
title: "System Error",
body: "",
show: true,
timeout: 10000,
actions: [],
level: "error",
}
if (error instanceof Error && error?.message) {
notificationObj.body = error.message;
} else if (typeof error === "string") {
notificationObj.body = error;
} else if (error?.stderr) {
notificationObj.body = error.stderr;
} else {
notificationObj.body = "An error occured, check the system console (CTRL+SHIFT+J) for more information.";
}
if (notificationFIFO.getLen() < 10)
notificationFIFO.push(notificationObj);
else
throw error;
}
const app = createApp(App, { notificationFIFO }).use(router);
app.config.errorHandler = (error) => errorHandler(error);
window.onerror = (...args) => errorHandler(args[4] ?? args[0]);
app.mount('#app');

69
navigator-vue/src/mode.js Normal file
View File

@ -0,0 +1,69 @@
import { useSpawn } from '@45drives/cockpit-helpers';
/** Run test with given expression and return boolean result. Throws on abnormal errors.
*
* @param {String} check - Argument(s) to test for checking path (man test(1))
* @param {String} path - Path to check
* @param {Object} opts - Options for cockpit.spawn()
* @returns {Promise<Boolean>} Result of test
*/
export const test = async (check, path, opts = { superuser: 'try' }) => {
try {
await useSpawn(['test', ...check.split(/\s+/), path], opts).promise();
return true;
} catch (state) {
if (state.status === 1)
return false;
throw new Error("Failed to check path: " + path + ": " + errorString(state));
}
}
/** Returns true if path exists, false otherwise. Throws on abnormal errors.
*
* @param {String} path - Path to check
* @param {Object} opts - Options for cockpit.spawn()
* @returns {Promise<Boolean>} Result of test
*/
export const checkIfExists = (path, opts = { superuser: 'try' }) => {
return test('-e', path, opts);
}
/**
* @typedef {Object} PermissionsResult
* @property {Boolean} r - Read permission result
* @property {Boolean} w - Write permission result
* @property {Boolean} x - Execute/search permission result
*/
/** Get read, write, and execute permissions for user on path. Throws on abnormal errors.
*
* @param {String} path - Path to check
* @param {Object} opts - Options for cockpit.spawn()
* @returns {Promise<PermissionsResult>} - result of test
*/
export const testPerimssions = async (path, opts = { superuser: 'try' }) => {
return {
r: await test('-r', path, opts),
w: await test('-w', path, opts),
x: await test('-x', path, opts),
}
}
/** Check for rw permissions if path is file or rwx permissions if path is dir.
* Returns false if DNE. Throws on abnormal errors.
*
* @param {String} path - Path to check
* @param {Boolean} readOnly - Set to true to ignore write permissions
* @returns {Promise<Boolean>} Result of check
*/
export const checkIfAllowed = async (path, readOnly = false) => {
if (!await checkIfExists(path))
return false;
const permissions = await testPerimssions(path);
let result = permissions.r;
if (!readOnly)
result &= permissions.w;
if (await test('-d', path))
result &= permissions.x;
return result;
}

View File

@ -0,0 +1,37 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import { lastPathStorageKey } from '../keys';
const routes = [
{
path: '/browse:path(.+)',
name: 'browse',
component: () => import('../views/Browser.vue'),
},
{
path: '/edit/:path(.*)',
name: 'edit',
component: () => import('../views/Editor.vue'),
},
{
path: '/:pathMatch(.*)*',
name: 'redirectToBrowse',
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
console.log(to);
if (to.name === 'redirectToBrowse') {
const lastLocation = localStorage.getItem(lastPathStorageKey) ?? '/';
next(`/browse${lastLocation}`);
cockpit.location.go(`/browse${lastLocation}`);
} else {
next();
}
})
export default router;

View File

@ -0,0 +1,199 @@
<template>
<div class="grow overflow-hidden">
<div class="h-full flex flex-col items-stretch">
<div class="flex gap-buttons items-stretch divide-x divide-default">
<div class="button-group-row px-4 py-2">
<button
class="p-2 rounded-lg hover:bg-accent relative"
:disabled="!pathHistory.backAllowed()"
@click="back()"
@mouseenter="backHistoryDropdown.mouseEnter"
@mouseleave="backHistoryDropdown.mouseLeave"
>
<ArrowLeftIcon class="size-icon icon-default" />
<ChevronDownIcon
class="w-3 h-3 icon-default absolute bottom-1 right-1"
v-if="pathHistory.backAllowed()"
/>
<div
v-if="backHistoryDropdown.showDropdown"
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80"
>
<div
v-for="item, index in pathHistory.stack.slice(0, pathHistory.index).reverse()"
:key="index"
@click="pathHistory.index = pathHistory.index - index"
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap"
>{{ item }}</div>
</div>
</button>
<button
class="p-2 rounded-lg hover:bg-accent relative"
:disabled="!pathHistory.forwardAllowed()"
@click="forward()"
@mouseenter="forwardHistoryDropdown.mouseEnter"
@mouseleave="forwardHistoryDropdown.mouseLeave"
>
<ArrowRightIcon class="size-icon icon-default" />
<ChevronDownIcon
class="w-3 h-3 icon-default absolute bottom-1 right-1"
v-if="pathHistory.forwardAllowed()"
/>
<div
v-if="forwardHistoryDropdown.showDropdown"
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80"
>
<div
v-for="item, index in pathHistory.stack.slice(pathHistory.index + 1)"
:key="index"
@click="pathHistory.index = pathHistory.index + index"
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap"
>{{ item }}</div>
</div>
</button>
<button class="p-2 rounded-lg hover:bg-accent" @click="up()">
<ArrowUpIcon class="size-icon icon-default" />
</button>
<button class="p-2 rounded-lg hover:bg-accent" @click="directoryViewRef.getEntries()">
<RefreshIcon class="size-icon icon-default" />
</button>
</div>
<div class="grow card-header px-4 py-2">
<PathBreadCrumbs :path="pathHistory.current() ?? '/'" @cd="newPath => cd(newPath)" />
</div>
</div>
<div class="grow overflow-hidden">
<DirectoryView
:path="pathHistory.current() ?? '/'"
@cd="newPath => cd(newPath)"
@updateStats="stats => $emit('updateFooterText', `${stats.files} file${stats.files === 1 ? '' : 's'}, ${stats.dirs} director${stats.dirs === 1 ? 'y' : 'ies'}`)"
ref="directoryViewRef"
/>
</div>
</div>
</div>
</template>
<script>
import { inject, ref, reactive, watch, nextTick } from 'vue';
import { useRoute } from "vue-router";
import DirectoryView from "../components/DirectoryView.vue";
import { errorStringHTML, canonicalPath } from '@45drives/cockpit-helpers';
import PathBreadCrumbs from '../components/PathBreadCrumbs.vue';
import { checkIfExists, checkIfAllowed } from '../mode';
import { notificationsInjectionKey, pathHistoryInjectionKey, lastPathStorageKey } from '../keys';
import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, RefreshIcon, ChevronDownIcon } from '@heroicons/vue/solid';
export default {
setup() {
const notifications = inject(notificationsInjectionKey);
const route = useRoute();
const pathHistory = inject(pathHistoryInjectionKey);
const directoryViewRef = ref();
const backHistoryDropdown = reactive({
showDropdown: false,
timeoutHandle: null,
mouseEnter: () => {
backHistoryDropdown.timeoutHandle = setTimeout(() => backHistoryDropdown.showDropdown = true, 500);
forwardHistoryDropdown.mouseLeave();
},
mouseLeave: () => {
if (backHistoryDropdown.timeoutHandle)
clearTimeout(backHistoryDropdown.timeoutHandle);
backHistoryDropdown.timeoutHandle = null;
backHistoryDropdown.showDropdown = false;
}
});
const forwardHistoryDropdown = reactive({
showDropdown: false,
timeoutHandle: null,
mouseEnter: () => {
forwardHistoryDropdown.timeoutHandle = setTimeout(() => forwardHistoryDropdown.showDropdown = true, 500);
backHistoryDropdown.mouseLeave();
},
mouseLeave: () => {
if (forwardHistoryDropdown.timeoutHandle)
clearTimeout(forwardHistoryDropdown.timeoutHandle);
forwardHistoryDropdown.timeoutHandle = null;
forwardHistoryDropdown.showDropdown = false;
}
});
const cd = (newPath, saveHistory = true) => {
localStorage.setItem(lastPathStorageKey, newPath);
if (saveHistory)
pathHistory.push(newPath);
cockpit.location.go(`/browse${newPath}`);
};
const back = (saveHistory = false) => {
cd(pathHistory.back() ?? '/', saveHistory);
}
const forward = (saveHistory = false) => {
cd(pathHistory.forward() ?? '/', saveHistory);
}
const up = (saveHistory = true) => {
cd(canonicalPath((pathHistory.current() ?? "") + '/..'), saveHistory);
}
watch(() => route.params.path, async () => {
if (!route.params.path)
return cockpit.location.go('/browse/');
const tmpPath = canonicalPath(route.params.path);
if (pathHistory.current() !== tmpPath) {
pathHistory.push(tmpPath);
}
try {
let badPath = false;
if (!await checkIfExists(tmpPath)) {
notifications.value.constructNotification("Failed to open path", `${tmpPath} does not exist.`, 'error');
badPath = true;
} else if (!await checkIfAllowed(tmpPath, true)) {
notifications.value.constructNotification("Failed to open path", `Permission denied for ${tmpPath}`, 'denied');
badPath = true;
}
if (badPath) {
if (pathHistory.backAllowed())
back();
else
up(false);
} else {
localStorage.setItem(lastPathStorageKey, tmpPath);
}
} catch (error) {
notifications.value.constructNotification("Failed to open path", errorStringHTML(error), 'error');
if (pathHistory.backAllowed())
back();
else
up(false);
}
}, { immediate: true });
return {
cockpit,
pathHistory,
directoryViewRef,
backHistoryDropdown,
forwardHistoryDropdown,
cd,
back,
forward,
up,
}
},
components: {
DirectoryView,
PathBreadCrumbs,
ArrowLeftIcon,
ArrowRightIcon,
ArrowUpIcon,
RefreshIcon,
ChevronDownIcon,
},
emits: [
'updateFooterText'
],
}
</script>

View File

View File

@ -0,0 +1,23 @@
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {
fontFamily: {
redhat: ['Red Hat Text', 'open-sans', 'sans-serif'],
"source-sans-pro": ["Source Sans Pro", 'open-sans', 'sans-serif'],
},
colors: {
neutral: {
850: "#222222",
}
}
},
},
plugins: [
require('@tailwindcss/forms'),
],
darkMode: "class",
}

View File

@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: "./",
})

805
navigator-vue/yarn.lock Normal file
View File

@ -0,0 +1,805 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@45drives/cockpit-css@^0.1.5":
version "0.1.12"
resolved "https://npm.pkg.github.com/download/@45drives/cockpit-css/0.1.12/b5d0b428714271695e5e0af9932b87e26456156e53aa99fbbb4431be0c4c8880#42ecb717da68e4c31454b5bc6019e3a4fe7dcd6c"
integrity sha512-RgyedX/5GuriDvLruvmbqb++cIBqIF5py2E4G8/wMtQfUf6QnJqsRLYNp7uzKcLpE2+kjm9pGASdvwV2I2b/YA==
"@45drives/cockpit-helpers@^0.1.2", "@45drives/cockpit-helpers@^0.1.8":
version "0.1.9"
resolved "https://npm.pkg.github.com/download/@45drives/cockpit-helpers/0.1.9/1b89ca1db889b979029f709808f6e19f277274687056ee600396f2c17a8c51c9#2e6cbded551aa4f9d0f79f6d101fc2514fc58c29"
integrity sha512-KNrPpcC5ipra+JWkKgBs18dAHUFdHDsce0sRI4Nb5Q8nwdWoaVMt5TEbIorRxxI5VlfhcYCl1xfDl+WSNKCvLw==
"@45drives/cockpit-syntaxes@^0.1.4":
version "0.1.4"
resolved "https://npm.pkg.github.com/download/@45drives/cockpit-syntaxes/0.1.4/e1d35e98f856398975e654f43776068aa511bf86906e97f653c94735993821c9#b386242f43e353ecc700ecba1d10a4d9ee6efca3"
integrity sha512-8mDVoiuV9Wp5NO7QmY6DHgU8lKqwO++sVEzKsWuyrHdG+6q4afGd2iu/2OD0GMLvzakeN5DJGt97s7RErbgkVQ==
dependencies:
"@45drives/cockpit-helpers" "^0.1.2"
"@45drives/regex@^0.1.0":
version "0.1.0"
resolved "https://npm.pkg.github.com/download/@45drives/regex/0.1.0/0f5192aa2141b3cb3d2e41b12f1003bbb880f5552f71a776b74bad0f2e6ef15b#5bd4aaeb4f28521de4551869cd88e12d9f61f065"
integrity sha512-YHjFxJv8vKoG+Jm5K5Q9/myz7lmenKmKyx4Cw04VJP+imTW+oYe8He41jibF1JSUqgZXOSOBWf4HpUcFaTKfmA==
"@babel/parser@^7.16.4":
version "7.17.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78"
integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==
"@fontsource/red-hat-text@^4.5.4":
version "4.5.9"
resolved "https://registry.yarnpkg.com/@fontsource/red-hat-text/-/red-hat-text-4.5.9.tgz#08c095082ddc5bdd638c99d7b770717b4af6de8e"
integrity sha512-7jBq3vSTtZt1kJpwRt04EFfAGepY2E9eq7p09zyD4Ibx0rItBy/uoF/6Wmx/1heJurbUxxcBHY/6dKNxBRpb4g==
"@headlessui/vue@^1.5.0":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@headlessui/vue/-/vue-1.6.1.tgz#772dde92ec3a3a494c827b1643a6d5538baabd29"
integrity sha512-fccGN1hsFvS0P73NplZoOdX1rhyaH5jIQE9IUKNaaJC72mQjR3lVvZldo+iiLF5X1bqgl6pDixWdqS2kMGfFxA==
"@heroicons/vue@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@heroicons/vue/-/vue-1.0.6.tgz#d8b90734b436eb5a87f40cc300b64a0fb0031f7f"
integrity sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@tailwindcss/forms@^0.5.0":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.1.tgz#7fe86b9b67e6d91cb902e2d3f4ebe561cc057a13"
integrity sha512-QSwsFORnC2BAP0lRzQkz1pw+EzIiiPdk4e27vGQjyXkwJPeC7iLIRVndJzf9CJVbcrrIcirb/TfxF3gRTyFEVA==
dependencies:
mini-svg-data-uri "^1.2.3"
"@vitejs/plugin-vue@^2.2.0":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz#fbf80cc039b82ac21a1acb0f0478de8f61fbf600"
integrity sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==
"@vue/compiler-core@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz#e915d59cce85898f5c5cfebe4c09e539278c3d59"
integrity sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.33"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz#6db84296f949f18e5d3e7fd5e80f943dbed7d5ec"
integrity sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==
dependencies:
"@vue/compiler-core" "3.2.33"
"@vue/shared" "3.2.33"
"@vue/compiler-sfc@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz#7ce01dc947a8b76c099811dc6ca58494d4dc773d"
integrity sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.33"
"@vue/compiler-dom" "3.2.33"
"@vue/compiler-ssr" "3.2.33"
"@vue/reactivity-transform" "3.2.33"
"@vue/shared" "3.2.33"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-ssr@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz#3e820267e4eea48fde9519f006dedca3f5e42e71"
integrity sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==
dependencies:
"@vue/compiler-dom" "3.2.33"
"@vue/shared" "3.2.33"
"@vue/devtools-api@^6.0.0":
version "6.1.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz#b4aec2f4b4599e11ba774a50c67fa378c9824e53"
integrity sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==
"@vue/reactivity-transform@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz#286063f44ca56150ae9b52f8346a26e5913fa699"
integrity sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.33"
"@vue/shared" "3.2.33"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.33.tgz#c84eedb5225138dbfc2472864c151d3efbb4b673"
integrity sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==
dependencies:
"@vue/shared" "3.2.33"
"@vue/runtime-core@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz#2df8907c85c37c3419fbd1bdf1a2df097fa40df2"
integrity sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==
dependencies:
"@vue/reactivity" "3.2.33"
"@vue/shared" "3.2.33"
"@vue/runtime-dom@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz#123b8969247029ea0d9c1983676d4706a962d848"
integrity sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==
dependencies:
"@vue/runtime-core" "3.2.33"
"@vue/shared" "3.2.33"
csstype "^2.6.8"
"@vue/server-renderer@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz#4b45d6d2ae10ea4e3d2cf8e676804cf60f331979"
integrity sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==
dependencies:
"@vue/compiler-ssr" "3.2.33"
"@vue/shared" "3.2.33"
"@vue/shared@3.2.33":
version "3.2.33"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e"
integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==
acorn-node@^1.6.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^7.0.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb"
integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==
autoprefixer@^10.4.2:
version "10.4.7"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf"
integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==
dependencies:
browserslist "^4.20.3"
caniuse-lite "^1.0.30001335"
fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
browserslist@^4.20.3:
version "4.20.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
dependencies:
caniuse-lite "^1.0.30001332"
electron-to-chromium "^1.4.118"
escalade "^3.1.1"
node-releases "^2.0.3"
picocolors "^1.0.0"
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335:
version "1.0.30001340"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001340.tgz#029a2f8bfc025d4820fafbfaa6259fd7778340c7"
integrity sha512-jUNz+a9blQTQVu4uFcn17uAD8IDizPzQkIKh3LCJfg9BkyIqExYYdyc/ZSlWUSKb8iYiXxKsxbv4zYSvkqjrxw==
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
color-name@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^2.6.8:
version "2.6.20"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
detective@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==
dependencies:
acorn-node "^1.6.1"
defined "^1.0.0"
minimist "^1.1.1"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
electron-to-chromium@^1.4.118:
version "1.4.137"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz#186180a45617283f1c012284458510cd99d6787f"
integrity sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==
esbuild-android-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21"
integrity sha512-EJOu04p9WgZk0UoKTqLId9VnIsotmI/Z98EXrKURGb3LPNunkeffqQIkjS2cAvidh+OK5uVrXaIP229zK6GvhQ==
esbuild-android-arm64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.39.tgz#f608d00ea03fe26f3b1ab92a30f99220390f3071"
integrity sha512-+twajJqO7n3MrCz9e+2lVOnFplRsaGRwsq1KL/uOy7xK7QdRSprRQcObGDeDZUZsacD5gUkk6OiHiYp6RzU3CA==
esbuild-darwin-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.39.tgz#31528daa75b4c9317721ede344195163fae3e041"
integrity sha512-ImT6eUw3kcGcHoUxEcdBpi6LfTRWaV6+qf32iYYAfwOeV+XaQ/Xp5XQIBiijLeo+LpGci9M0FVec09nUw41a5g==
esbuild-darwin-arm64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.39.tgz#247f770d86d90a215fa194f24f90e30a0bd97245"
integrity sha512-/fcQ5UhE05OiT+bW5v7/up1bDsnvaRZPJxXwzXsMRrr7rZqPa85vayrD723oWMT64dhrgWeA3FIneF8yER0XTw==
esbuild-freebsd-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.39.tgz#479414d294905055eb396ebe455ed42213284ee0"
integrity sha512-oMNH8lJI4wtgN5oxuFP7BQ22vgB/e3Tl5Woehcd6i2r6F3TszpCnNl8wo2d/KvyQ4zvLvCWAlRciumhQg88+kQ==
esbuild-freebsd-arm64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.39.tgz#cedeb10357c88533615921ae767a67dc870a474c"
integrity sha512-1GHK7kwk57ukY2yI4ILWKJXaxfr+8HcM/r/JKCGCPziIVlL+Wi7RbJ2OzMcTKZ1HpvEqCTBT/J6cO4ZEwW4Ypg==
esbuild-linux-32@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.39.tgz#d9f008c4322d771f3958f59c1eee5a05cdf92485"
integrity sha512-g97Sbb6g4zfRLIxHgW2pc393DjnkTRMeq3N1rmjDUABxpx8SjocK4jLen+/mq55G46eE2TA0MkJ4R3SpKMu7dg==
esbuild-linux-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.39.tgz#ba58d7f66858913aeb1ab5c6bde1bbd824731795"
integrity sha512-4tcgFDYWdI+UbNMGlua9u1Zhu0N5R6u9tl5WOM8aVnNX143JZoBZLpCuUr5lCKhnD0SCO+5gUyMfupGrHtfggQ==
esbuild-linux-arm64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.39.tgz#708785a30072702b5b1c16b65cf9c25c51202529"
integrity sha512-23pc8MlD2D6Px1mV8GMglZlKgwgNKAO8gsgsLLcXWSs9lQsCYkIlMo/2Ycfo5JrDIbLdwgP8D2vpfH2KcBqrDQ==
esbuild-linux-arm@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.39.tgz#4e8b5deaa7ab60d0d28fab131244ef82b40684f4"
integrity sha512-t0Hn1kWVx5UpCzAJkKRfHeYOLyFnXwYynIkK54/h3tbMweGI7dj400D1k0Vvtj2u1P+JTRT9tx3AjtLEMmfVBQ==
esbuild-linux-mips64le@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.39.tgz#6f3bf3023f711084e5a1e8190487d2020f39f0f7"
integrity sha512-epwlYgVdbmkuRr5n4es3B+yDI0I2e/nxhKejT9H0OLxFAlMkeQZxSpxATpDc9m8NqRci6Kwyb/SfmD1koG2Zuw==
esbuild-linux-ppc64le@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.39.tgz#900e718a4ea3f6aedde8424828eeefdd4b48d4b9"
integrity sha512-W/5ezaq+rQiQBThIjLMNjsuhPHg+ApVAdTz2LvcuesZFMsJoQAW2hutoyg47XxpWi7aEjJGrkS26qCJKhRn3QQ==
esbuild-linux-riscv64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.39.tgz#dcbff622fa37047a75d2ff7a1d8d2949d80277e4"
integrity sha512-IS48xeokcCTKeQIOke2O0t9t14HPvwnZcy+5baG13Z1wxs9ZrC5ig5ypEQQh4QMKxURD5TpCLHw2W42CLuVZaA==
esbuild-linux-s390x@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.39.tgz#3f725a7945b419406c99d93744b28552561dcdfd"
integrity sha512-zEfunpqR8sMomqXhNTFEKDs+ik7HC01m3M60MsEjZOqaywHu5e5682fMsqOlZbesEAAaO9aAtRBsU7CHnSZWyA==
esbuild-netbsd-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.39.tgz#e10e40b6a765798b90d4eb85901cc85c8b7ff85e"
integrity sha512-Uo2suJBSIlrZCe4E0k75VDIFJWfZy+bOV6ih3T4MVMRJh1lHJ2UyGoaX4bOxomYN3t+IakHPyEoln1+qJ1qYaA==
esbuild-openbsd-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.39.tgz#935ec143f75ce10bd9cdb1c87fee00287eb0edbc"
integrity sha512-secQU+EpgUPpYjJe3OecoeGKVvRMLeKUxSMGHnK+aK5uQM3n1FPXNJzyz1LHFOo0WOyw+uoCxBYdM4O10oaCAA==
esbuild-sunos-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.39.tgz#0e7aa82b022a2e6d55b0646738b2582c2d72c3c0"
integrity sha512-qHq0t5gePEDm2nqZLb+35p/qkaXVS7oIe32R0ECh2HOdiXXkj/1uQI9IRogGqKkK+QjDG+DhwiUw7QoHur/Rwg==
esbuild-windows-32@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.39.tgz#3f1538241f31b538545f4b5841b248cac260fa35"
integrity sha512-XPjwp2OgtEX0JnOlTgT6E5txbRp6Uw54Isorm3CwOtloJazeIWXuiwK0ONJBVb/CGbiCpS7iP2UahGgd2p1x+Q==
esbuild-windows-64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.39.tgz#b100c59f96d3c2da2e796e42fee4900d755d3e03"
integrity sha512-E2wm+5FwCcLpKsBHRw28bSYQw0Ikxb7zIMxw3OPAkiaQhLVr3dnVO8DofmbWhhf6b97bWzg37iSZ45ZDpLw7Ow==
esbuild-windows-arm64@0.14.39:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.39.tgz#00268517e665b33c89778d61f144e4256b39f631"
integrity sha512-sBZQz5D+Gd0EQ09tZRnz/PpVdLwvp/ufMtJ1iDFYddDaPpZXKqPyaxfYBLs3ueiaksQ26GGa7sci0OqFzNs7KA==
esbuild@^0.14.27:
version "0.14.39"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.39.tgz#c926b2259fe6f6d3a94f528fb42e103c5a6d909a"
integrity sha512-2kKujuzvRWYtwvNjYDY444LQIA3TyJhJIX3Yo4+qkFlDDtGlSicWgeHVJqMUP/2sSfH10PGwfsj+O2ro1m10xQ==
optionalDependencies:
esbuild-android-64 "0.14.39"
esbuild-android-arm64 "0.14.39"
esbuild-darwin-64 "0.14.39"
esbuild-darwin-arm64 "0.14.39"
esbuild-freebsd-64 "0.14.39"
esbuild-freebsd-arm64 "0.14.39"
esbuild-linux-32 "0.14.39"
esbuild-linux-64 "0.14.39"
esbuild-linux-arm "0.14.39"
esbuild-linux-arm64 "0.14.39"
esbuild-linux-mips64le "0.14.39"
esbuild-linux-ppc64le "0.14.39"
esbuild-linux-riscv64 "0.14.39"
esbuild-linux-s390x "0.14.39"
esbuild-netbsd-64 "0.14.39"
esbuild-openbsd-64 "0.14.39"
esbuild-sunos-64 "0.14.39"
esbuild-windows-32 "0.14.39"
esbuild-windows-64 "0.14.39"
esbuild-windows-arm64 "0.14.39"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
dependencies:
reusify "^1.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.8.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
lilconfig@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
dependencies:
sourcemap-codec "^1.4.8"
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
dependencies:
braces "^3.0.2"
picomatch "^2.3.1"
mini-svg-data-uri@^1.2.3:
version "1.4.4"
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
minimist@^1.1.1:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
nanoid@^3.3.3:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
node-releases@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-range@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
dependencies:
camelcase-css "^2.0.1"
postcss-load-config@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
dependencies:
lilconfig "^2.0.5"
yaml "^1.10.2"
postcss-nested@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
dependencies:
postcss-selector-parser "^6.0.6"
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.1.10, postcss@^8.4.12, postcss@^8.4.13, postcss@^8.4.8:
version "8.4.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
dependencies:
nanoid "^3.3.3"
picocolors "^1.0.0"
source-map-js "^1.0.2"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
dependencies:
is-core-module "^2.8.1"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rollup@^2.59.0:
version "2.72.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.72.1.tgz#861c94790537b10008f0ca0fbc60e631aabdd045"
integrity sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==
optionalDependencies:
fsevents "~2.3.2"
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-sans-pro@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/source-sans-pro/-/source-sans-pro-3.6.0.tgz#f4167065ebf096136b1b4f141dbd48886dda1486"
integrity sha512-C1RFUGu+YASuqpgDRInTM7Y6OwqeWNOuKn7v0P/4Kh66epTI4PYWwPWP5kdA4l/VqzBAWiqoz5dk0trof73R7w==
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tailwindcss@^3.0.23:
version "3.0.24"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d"
integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==
dependencies:
arg "^5.0.1"
chokidar "^3.5.3"
color-name "^1.1.4"
detective "^5.2.0"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.11"
glob-parent "^6.0.2"
is-glob "^4.0.3"
lilconfig "^2.0.5"
normalize-path "^3.0.0"
object-hash "^3.0.0"
picocolors "^1.0.0"
postcss "^8.4.12"
postcss-js "^4.0.0"
postcss-load-config "^3.1.4"
postcss-nested "5.0.6"
postcss-selector-parser "^6.0.10"
postcss-value-parser "^4.2.0"
quick-lru "^5.1.1"
resolve "^1.22.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
vite@^2.8.0:
version "2.9.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e"
integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==
dependencies:
esbuild "^0.14.27"
postcss "^8.4.13"
resolve "^1.22.0"
rollup "^2.59.0"
optionalDependencies:
fsevents "~2.3.2"
vue-router@^4.0.15:
version "4.0.15"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.15.tgz#b4a0661efe197f8c724e0f233308f8776e2c3667"
integrity sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==
dependencies:
"@vue/devtools-api" "^6.0.0"
vue@^3.2.25:
version "3.2.33"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.33.tgz#7867eb16a3293a28c4d190a837bc447878bd64c2"
integrity sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==
dependencies:
"@vue/compiler-dom" "3.2.33"
"@vue/compiler-sfc" "3.2.33"
"@vue/runtime-dom" "3.2.33"
"@vue/server-renderer" "3.2.33"
"@vue/shared" "3.2.33"
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==