Stop using script setup feature but use olden style

This commit is contained in:
Johannes Zellner
2023-02-22 15:59:23 +01:00
parent 669b042107
commit dafc8dea19
501 changed files with 26308 additions and 106 deletions
+16 -14
View File
@@ -1,20 +1,22 @@
<script setup>
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
let accessToken = localStorage.accessToken || '';
if (!accessToken) router.push('/login');
else router.push('/home');
</script>
<template>
<router-view></router-view>
</template>
<script>
export default {
data() {
return {
accessToken: localStorage.accessToken || ''
};
},
mounted() {
if (!this.accessToken) this.$router.push('/login');
else this.$router.push('/home');
}
};
</script>
<style scoped>
</style>
+93
View File
@@ -0,0 +1,93 @@
<template>
<div class="directory-view">
<div class="directory-view-header">
<div class="directory-view-header-icon"></div>
<div class="directory-view-header-name">Name</div>
<div class="directory-view-header-size">Size</div>
<div class="directory-view-header-modified">Modified</div>
</div>
<div class="directory-view-body-container">
<div class="directory-view-body">
<DirectoryViewListItem v-for="item in items" :item="item"/>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import Button from 'primevue/button';
import DirectoryViewListItem from './DirectoryViewListItem.vue';
export default {
name: 'DirectoryView',
data() {
return {
items: []
};
},
components: {
DirectoryViewListItem
},
mounted() {
// fill with fake items
for (let i = 0; i < 100; ++i) {
this.items.push({
type: i < 20 ? 'directory' : 'file',
name: 'Entry ' + i,
size: parseInt(Math.random() * 1000000),
modified: new Date(+(new Date()) - Math.floor(Math.random() * 10000000000)),
});
}
}
};
</script>
<style scoped>
.directory-view {
background-color: white;
display: block;
overflow: hidden;
height: 100%;
width: 100%;
}
.directory-view-header {
padding: 10px;
display: flex;
}
.directory-view-body-container {
overflow: hidden;
height: calc(100% - 38px); /* 38px is the header size */
}
.directory-view-body {
padding: 0 10px;
height: 100%;
overflow: scroll;
}
.directory-view-header-icon {
width: 40px;
}
.directory-view-header-name {
padding-left: 10px;
flex-grow: 1;
}
.directory-view-header-size {
width: 100px;
}
.directory-view-header-modified {
width: 100px;
}
</style>
+77
View File
@@ -0,0 +1,77 @@
<template>
<div class="row">
<div class="col icon"><img :src="item.type === 'file' ? '/mime-types/text-x-plain.svg' : '/mime-types/inode-directory.svg'"/></div>
<div class="col label">{{ item.name }}</div>
<div class="col size">{{ prettyFileSize(item.size) }}</div>
<div class="col modified">{{ prettyDate(item.modified) }}</div>
</div>
</template>
<script>
import { prettyDate, prettyFileSize } from '../utils';
export default {
name: 'DirectoryViewListItem',
props: {
item: Object
},
data() {
return {};
},
methods: {
prettyFileSize,
prettyDate
},
mounted() {
}
};
</script>
<style scoped>
.row {
display: flex;
overflow: hidden;
height: 40px;
width: 100%;
line-height: 40px;
}
.row:hover {
background-color: whitesmoke;
}
.col {
white-space: nowrap;
}
.icon {
height: 40px;
width: 40px;
}
.icon > img {
width: 32px;
height: 32px;
object-fit: cover;
vertical-align: middle;
}
.label {
padding-left: 5px;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.size {
width: 100px;
}
.modified {
width: 100px;
}
</style>
-27
View File
@@ -1,27 +0,0 @@
<script setup>
import { useRouter } from 'vue-router';
import Button from 'primevue/button';
const router = useRouter();
if (!localStorage.accessToken) router.push('/login');
function onLogout() {
delete localStorage.accessToken;
router.push('/login');
}
</script>
<template>
<p>You are logged in</p>
<Button label="Logout" @click="onLogout"/>
</template>
<style scoped>
</style>
+2 -2
View File
@@ -10,8 +10,8 @@ import PrimeVue from 'primevue/config';
import { createRouter, createWebHashHistory } from 'vue-router';
import App from './App.vue';
import Login from './components/Login.vue';
import Home from './components/Home.vue';
import Login from './views/Login.vue';
import Home from './views/Home.vue';
const routes = [
{ path: '/home', component: Home },
+196
View File
@@ -0,0 +1,196 @@
import { filesize } from 'filesize';
function prettyDate(value) {
var date = new Date(value),
diff = (((new Date()).getTime() - date.getTime()) / 1000),
day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0)
return;
return day_diff === 0 && (
diff < 60 && 'just now' ||
diff < 120 && '1 min ago' ||
diff < 3600 && Math.floor( diff / 60 ) + ' min ago' ||
diff < 7200 && '1 hour ago' ||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
day_diff === 1 && 'Yesterday' ||
day_diff < 7 && day_diff + ' days ago' ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' ||
day_diff < 365 && Math.round( day_diff / 30 ) + ' months ago' ||
Math.round( day_diff / 365 ) + ' years ago';
}
function prettyLongDate(value) {
if (!value) return 'unkown';
var date = new Date(value);
return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
}
function prettyFileSize(value) {
if (typeof value !== 'number') return 'unkown';
return filesize(value);
}
function sanitize(path) {
path = '/' + path;
return path.replace(/\/+/g, '/');
}
function encode(path) {
return path.split('/').map(encodeURIComponent).join('/');
}
function decode(path) {
return path.split('/').map(decodeURIComponent).join('/');
}
// TODO create share links instead of using access token
function getDirectLink(entry) {
if (entry.share) {
let link = window.location.origin + '/api/v1/shares/' + entry.share.id + '?type=raw&path=' + encodeURIComponent(entry.filePath);
return link;
} else {
return window.location.origin + '/api/v1/files?type=raw&path=' + encodeURIComponent(entry.filePath);
}
}
// TODO the url might actually return a 412 in which case we have to keep reloading
function getPreviewUrl(entry) {
if (!entry.previewUrl) return '';
return entry.previewUrl;
}
function getShareLink(shareId) {
return window.location.origin + '/api/v1/shares/' + shareId + '?type=raw';
}
function download(entries, name) {
if (!entries.length) return;
if (entries.length === 1) {
if (entries[0].share) window.location.href = '/api/v1/shares/' + entries[0].share.id + '?type=download&path=' + encodeURIComponent(entries[0].filePath);
else window.location.href = '/api/v1/files?type=download&path=' + encodeURIComponent(entries[0].filePath);
return;
}
const params = new URLSearchParams();
// be a bit smart about the archive name and folder tree
const folderPath = entries[0].filePath.slice(0, -entries[0].fileName.length);
const archiveName = name || folderPath.slice(folderPath.slice(0, -1).lastIndexOf('/')+1).slice(0, -1);
params.append('name', archiveName);
params.append('skipPath', folderPath);
params.append('entries', JSON.stringify(entries.map(function (entry) {
return {
filePath: entry.filePath,
shareId: entry.share ? entry.share.id : undefined
};
})));
window.location.href = '/api/v1/download?' + params.toString();
}
function getFileTypeGroup(entry) {
return entry.mimeType.split('/')[0];
}
// simple extension detection, does not work with double extension like .tar.gz
function getExtension(entry) {
if (entry.isFile) return entry.fileName.slice(entry.fileName.lastIndexOf('.') + 1);
return '';
}
function copyToClipboard(value) {
var elem = document.createElement('input');
elem.value = value;
document.body.append(elem);
elem.select();
document.execCommand('copy');
elem.remove();
}
function clearSelection() {
if(document.selection && document.selection.empty) {
document.selection.empty();
} else if(window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
}
}
function urlSearchQuery() {
return decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
}
// those paths contain the internal type and path reference eg. shares/:shareId/folder/filename or files/folder/filename
function parseResourcePath(resourcePath) {
var result = {
type: '',
path: '',
shareId: '',
apiPath: '',
resourcePath: ''
};
if (resourcePath.indexOf('files/') === 0) {
result.type = 'files';
result.path = resourcePath.slice('files'.length) || '/';
result.apiPath = '/api/v1/files';
result.resourcePath = result.type + result.path;
} else if (resourcePath.indexOf('shares/') === 0) {
result.type = 'shares';
result.shareId = resourcePath.split('/')[1];
result.path = resourcePath.slice((result.type + '/' + result.shareId).length) || '/';
result.apiPath = '/api/v1/shares/' + result.shareId;
// without shareId we show the root (share listing)
result.resourcePath = result.type + '/' + (result.shareId ? (result.shareId + result.path) : '');
} else {
console.error('Unknown resource path', resourcePath);
}
return result;
}
function getEntryIdentifier(entry) {
return (entry.share ? (entry.share.id + '/') : '') + entry.filePath;
}
function entryListSort(list, prop, desc) {
var tmp = list.sort(function (a, b) {
var av = a[prop];
var bv = b[prop];
if (typeof av === 'string') return (av.toUpperCase() < bv.toUpperCase()) ? -1 : 1;
else return (av < bv) ? -1 : 1;
});
if (desc) return tmp;
return tmp.reverse();
}
export {
getDirectLink,
getPreviewUrl,
getShareLink,
getFileTypeGroup,
prettyDate,
prettyLongDate,
prettyFileSize,
sanitize,
encode,
decode,
download,
getExtension,
copyToClipboard,
clearSelection,
urlSearchQuery,
parseResourcePath,
getEntryIdentifier,
entryListSort
};
+42
View File
@@ -0,0 +1,42 @@
<template>
<div>
<div>
<span>You are logged in</span>
<Button label="Logout" @click="onLogout"/>
</div>
<div class="main-view">
<DirectoryView />
</div>
</div>
</template>
<script>
import Button from 'primevue/button';
import DirectoryView from '../components/DirectoryView.vue';
export default {
name: 'Home',
components: {
DirectoryView,
Button
},
methods: {
onLogout() {
delete localStorage.accessToken;
this.$router.push('/login');
}
}
};
</script>
<style scoped>
.main-view {
width: 100%;
height: 400px;
}
</style>
@@ -1,65 +1,3 @@
<script setup>
// this is run on every component instantiation, note <script setup> tag
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import superagent from 'superagent';
import safe from 'safetydance';
// if imported in script setup tag they are also registered with the vue app as components to be used in html
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import Password from 'primevue/password';
import InputMask from 'primevue/inputmask';
const router = useRouter();
// events the component can emit
const emit = defineEmits([ 'success', 'error' ]);
// can be exposed as env var, see develop.sh
// all local variables in the script setup tags are exposed to DOM elements
const BASE_URL = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
// these are reactive variables and exposed to the dom elements
const username = ref('');
const password = ref('');
const totpToken = ref('');
const error = ref('');
const busy = ref(false);
// exposed to dom elements normally as in angulare with $scope.onLogin
async function onLogin() {
error.value = false;
busy.value = true;
const [err, res] = await safe(superagent.post(`${BASE_URL}/api/v1/cloudron/login`).send({ username: username.value, password: password.value, totpToken: totpToken.value }));
busy.value = false;
if (err && err.status === 401) {
error.value = 'Invalid username or password';
password.value = '';
return;
}
if (err) return console.error(err);
localStorage.accessToken = res.body.accessToken;
router.push('/home');
emit('success', res.body.accessToken);
username.value = '';
password.value = '';
totpToken.value = '';
}
setTimeout(function () { document.getElementById('usernameInput').focus(); }, 0);
</script>
<template>
<div class="container">
<img style="margin-top: -84px" src="/logo.png" width="128" height="128" />
@@ -91,6 +29,69 @@ setTimeout(function () { document.getElementById('usernameInput').focus(); }, 0)
</div>
</template>
<script>
import superagent from 'superagent';
import safe from 'safetydance';
// if imported in script setup tag they are also registered with the vue app as components to be used in html
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import Password from 'primevue/password';
import InputMask from 'primevue/inputmask';
// can be exposed as env var, see develop.sh
const BASE_URL = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
export default {
components: {
Button, InputText, Password, InputMask
},
emits: [ 'success', 'error' ],
data() {
return {
username: '',
password: '',
totpToken: '',
error: '',
busy: false
};
},
methods: {
async onLogin() {
this.error = false;
this.busy = true;
const [err, res] = await safe(superagent.post(`${BASE_URL}/api/v1/cloudron/login`).send({ username: this.username, password: this.password, totpToken: this.totpToken }));
this.busy = false;
if (err && err.status === 401) {
this.error = 'Invalid username or password';
this.password = '';
return;
}
if (err) return console.error(err);
localStorage.accessToken = res.body.accessToken;
this.$router.push('/home');
this.$emit('success', res.body.accessToken);
this.username = '';
this.password = '';
this.totpToken = '';
}
},
mounted() {
setTimeout(function () { document.getElementById('usernameInput').focus(); }, 0);
}
};
</script>
<style scoped>
h1 {