feat(frontend): many things

really too many things changed for a commit message. just build this
This commit is contained in:
Dimitrie Stefanescu
2021-12-30 19:32:12 +00:00
parent b288e39e89
commit b3de85f193
12 changed files with 789 additions and 549 deletions
+295 -514
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -32,6 +32,7 @@
"vue-matomo": "^3.14.0-0",
"vue-router": "^3.4.9",
"vue-timeago": "^5.1.2",
"vue2-perfect-scrollbar": "^1.5.2",
"vuedraggable": "^2.24.3",
"vuetify": "^2.3.21",
"vuetify-image-input": "^19.1.0",
+6
View File
@@ -21,6 +21,11 @@ Vue.use(VueFilterDateParse)
import VueFilterDateFormat from '@vuejs-community/vue-filter-date-format'
Vue.use(VueFilterDateFormat)
import PerfectScrollbar from 'vue2-perfect-scrollbar'
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css'
Vue.use(PerfectScrollbar)
import VTooltip from 'v-tooltip'
Vue.use(VTooltip, { defaultDelay: 300 })
@@ -53,6 +58,7 @@ Vue.filter('capitalize', (value) => {
return value.charAt(0).toUpperCase() + value.slice(1)
})
// Event hub
Vue.prototype.$eventHub = new Vue()
// adds copy to clipboard on vue instance
+14 -1
View File
@@ -7,7 +7,9 @@
:class="`grey ${$vuetify.theme.dark ? 'darken-4' : 'lighten-4'} elevation-1`"
width="325"
>
<main-nav />
<!-- <perfect-scrollbar :options="{ suppressScrollX: true }"> -->
<main-nav />
<!-- </perfect-scrollbar> -->
<template v-if="$route.meta.showBottomNavActions" #append>
<main-nav-bottom />
</template>
@@ -84,3 +86,14 @@ export default {
}
}
</script>
<style scoped>
.ps {
height: 100%;
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
overflow-y: scroll;
}
.ps::-webkit-scrollbar {
display: none;
}
</style>
@@ -0,0 +1,183 @@
<template>
<div class="mt-3">
<portal to="filter-actions">
<v-list-item-action class="pa-0 ma-0">
<v-btn v-tooltip="''" small icon @click.stop="colorBy = !colorBy">
<v-icon small :class="`${colorBy ? 'primary--text' : ''}`">mdi-palette</v-icon>
</v-btn>
</v-list-item-action>
</portal>
<v-row
v-for="type in typeMap"
:key="type.fullName"
no-gutters
class="my-1 property-row rounded-lg"
>
<v-col
cols="1"
:class="`text-center text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
style="line-height: 24px; font-size: 9px"
>
{{ type.count }}
</v-col>
<v-col
v-tooltip="type.fullName"
cols="8"
:class="`caption text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
style="line-height: 24px"
>
{{ type.name }}
</v-col>
<v-col
cols="3"
:class="`caption text-truncate text-right px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
style="line-height: 24px"
>
<div
v-if="colorBy"
class="d-inline-block rounded"
:style="`width: 10px; height: 10px`"
></div>
<v-btn
v-tooltip="'Toggle visibility'"
x-small
icon
class="mr-1"
@click="toggleVisibility(type.fullName)"
>
<v-icon class="grey--text" style="font-size: 11px">
{{ hidden.indexOf(type.fullName) === -1 ? 'mdi-eye' : 'mdi-eye-off' }}
</v-icon>
</v-btn>
<v-btn
v-tooltip="'Isolate objects'"
x-small
icon
class="mr-1"
@click="toggleFilter(type.fullName)"
>
<v-icon
:class="`${filtered.indexOf(type.fullName) !== -1 ? 'primary--text' : 'grey--text'}`"
style="font-size: 11px"
>
{{ !filtered.indexOf(type.fullName) !== -1 ? 'mdi-filter' : 'mdi-filter' }}
</v-icon>
</v-btn>
</v-col>
</v-row>
</div>
</template>
<script>
export default {
components: {},
props: {
filter: {
type: Object,
default: () => null
},
active: { type: Boolean, default: false }
},
data() {
return {
hidden: [],
filtered: [],
typeMap: [],
colorBy: false,
appliedFilter: {},
legend: null
}
},
watch: {
filter(newVal) {
this.generateTypeMap(newVal)
},
async colorBy(newVal) {
this.appliedFilter.colorBy = newVal
? { type: 'category', property: this.filter.targetKey }
: null
let res = await window.__viewer.applyFilter(this.appliedFilter)
console.log(res)
this.mashColorLegend(res.colorLegend)
}
},
mounted() {
this.generateTypeMap(this.filter)
},
beforeDestroy() {
window.__viewer.applyFilter(null)
},
methods: {
mashColorLegend(thanksCristi) {
if (!thanksCristi) return
// TODO
},
async toggleFilter(type) {
let indx = this.filtered.indexOf(type)
if (indx === -1) this.filtered.push(type)
else this.filtered.splice(indx, 1)
this.hidden.splice(0, this.hidden.length)
if (this.filtered.length === 0) {
window.__viewer.applyFilter(
this.colorBy ? { colorBy: { type: 'category', property: this.filter.targetKey } } : null
)
this.appliedFilter = {}
} else {
let filterObj = {
filterBy: {},
colorBy: this.colorBy ? { type: 'category', property: this.filter.targetKey } : null,
ghostOthers: true
}
filterObj.filterBy[this.filter.targetKey] = this.filtered
let res = await window.__viewer.applyFilter(filterObj)
this.mashColorLegend(res.colorLegend)
this.appliedFilter = filterObj
}
},
async toggleVisibility(type) {
let indx = this.hidden.indexOf(type)
if (indx === -1) this.hidden.push(type)
else this.hidden.splice(indx, 1)
this.filtered.splice(0, this.filtered.length)
if (this.hidden.length === 0) {
window.__viewer.applyFilter(
this.colorBy ? { colorBy: { type: 'category', property: this.filter.targetKey } } : null
)
this.appliedFilter = {}
} else {
let filterObj = {
filterBy: {},
colorBy: this.colorBy ? { type: 'category', property: this.filter.targetKey } : null,
ghostOthers: false
}
filterObj.filterBy[this.filter.targetKey] = { not: this.hidden }
let res = await window.__viewer.applyFilter(filterObj)
this.mashColorLegend(res.colorLegend)
this.appliedFilter = filterObj
}
},
generateTypeMap(filter) {
if (filter.data.type !== 'string') return []
let typeMap = []
for (let key of Object.keys(filter.data.uniqueValues)) {
let shortName = key.split('.').reverse()[0]
typeMap.push({
name: shortName,
fullName: key,
count: filter.data.uniqueValues[key]
})
}
this.typeMap.splice(0, this.typeMap.length)
this.typeMap.push(...typeMap)
}
}
}
</script>
<style scoped>
.property-row {
transition: all 0.3s ease;
background: rgba(120, 120, 120, 0.05);
}
.property-row:hover {
background: rgba(120, 120, 120, 0.09);
}
</style>
@@ -0,0 +1,119 @@
<template>
<v-row no-gutters class="my-1 property-row rounded-lg">
<v-col cols="1" class="text-center" style="line-height: 30px">
<v-icon small style="font-size: 12px" :class="`${$vuetify.theme.dark ? 'grey--text' : ''}`">
{{ filter.data.type === 'number' ? 'mdi-numeric' : 'mdi-format-text' }}
</v-icon>
</v-col>
<v-col
v-tooltip="filter.targetKey"
cols="8"
:class="`caption text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
style="line-height: 30px"
>
{{ filter.name }}
</v-col>
<v-col class="text-right" style="line-height: 30px">
<v-btn x-small @click="$emit('active-toggle', filter)">{{ active ? 'Remove' : 'Set' }}</v-btn>
</v-col>
<v-scroll-y-transition>
<v-col v-if="active && filter.data.type === 'string'" cols="12">
<!-- <div v-if="filter.data.type === 'string'"> -->
<v-row
v-for="type in typeMap"
:key="type.fullName"
no-gutters
class="my-1 property-row rounded-lg"
>
<v-col
cols="1"
:class="`caption text-center text-truncate px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px; font-size: 10px"
>
{{ type.count }}
</v-col>
<v-col
v-tooltip="type.fullName"
cols="8"
:class="`caption text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
style="line-height: 24px"
>
{{ type.name }}
</v-col>
<v-col
cols="3"
:class="`caption text-truncate text-right px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px"
>
<v-btn
v-tooltip="'Toggle visibility'"
x-small
icon
class="mr-1"
@click="toggleVisibility()"
>
<v-icon class="grey--text" style="font-size: 11px">
{{ visible ? 'mdi-eye' : 'mdi-eye-off' }}
</v-icon>
</v-btn>
<v-btn v-tooltip="'Isolate objects'" x-small icon class="mr-1" @click="toggleFilter()">
<v-icon
:class="`${filtered ? 'primary--text' : 'grey--text'}`"
style="font-size: 11px"
>
{{ !filtered ? 'mdi-filter' : 'mdi-filter' }}
</v-icon>
</v-btn>
</v-col>
</v-row>
<!-- </div> -->
</v-col>
</v-scroll-y-transition>
</v-row>
</template>
<script>
export default {
components: {},
props: {
filter: {
type: Object,
default: () => null
},
active: { type: Boolean, default: false }
},
data() {
return {
visible: true,
filtered: false
}
},
computed: {
typeMap() {
if (this.filter.data.type !== 'string') return []
let typeMap = []
for (let key of Object.keys(this.filter.data.uniqueValues)) {
let shortName = key.split('.').reverse()[0]
typeMap.push({
name: shortName,
fullName: key,
count: this.filter.data.uniqueValues[key]
})
}
return typeMap
}
}
}
</script>
<style scoped>
.property-row {
transition: all 0.3s ease;
background: rgba(120, 120, 120, 0.05);
}
.property-row:hover {
background: rgba(120, 120, 120, 0.09);
}
</style>
@@ -1,54 +1,196 @@
<template>
<v-list dense nav class="mt-0 py-0">
<v-list-item :class="`px-2 list-overlay`" active @click="expand = !expand">
<v-list dense nav class="mt-0 py-0 mb-3">
<v-list-item
:class="`px-2 list-overlay-${$vuetify.theme.dark ? 'dark' : 'light'} elevation-2`"
style="position: sticky; top: 64px"
@click="expand = !expand"
>
<v-list-item-action>
<v-icon small>mdi-filter-variant</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>Filters</v-list-item-title>
<v-list-item-title>
<span v-if="activeFilter === null">
Filters
<span class="caption grey--text">({{ allFilters.length }})</span>
</span>
<span v-else>{{ activeFilter.name }}</span>
</v-list-item-title>
</v-list-item-content>
<portal-target name="filter-actions"></portal-target>
<v-list-item-action v-if="activeFilter" class="pa-0 ma-0">
<v-btn
v-tooltip="'Remove filter'"
small
icon
@click.stop="
activeFilter = null
filterSearch = null
"
>
<v-icon small>mdi-close</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action class="pa-0 ma-0">
<v-btn small icon @click.stop="expand = !expand">
<v-icon>{{ expand ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
<v-expand-transition>
<v-scroll-y-transition>
<div v-show="expand">
<v-subheader>Suggested Filters</v-subheader>
<div class="px-2">Todo</div>
<v-subheader>All filters</v-subheader>
<div class="px-1">
<v-text-field
solo
dense
placeholder="Search filters"
append-icon="mdi-magnify"
hide-details
/>
<div v-if="activeFilter" class="px-0">
<filter-active :filter="activeFilter" />
</div>
<div v-show="activeFilter === null">
<v-subheader>TODO: reccommended filters</v-subheader>
<div class="">
<v-text-field
v-model="filterSearch"
solo
dense
placeholder="Search filters"
append-icon="mdi-magnify"
hide-details
class="my-2"
style="position: sticky; top: 110px; z-index: 6"
/>
<div v-for="filter in matchingFilters" :key="filter.targetKey">
<filter-single :filter="filter" @active-toggle="(e) => (activeFilter = e)" />
</div>
</div>
</div>
</div>
</v-expand-transition>
</v-scroll-y-transition>
</v-list>
</template>
<script>
export default {
components: {
FilterSingle: () => import('@/cleanup/components/viewer/FilterSingle'),
FilterActive: () => import('@/cleanup/components/viewer/FilterActive')
},
props: {
views: {
type: Array,
default: () => []
props: {
type: Object,
default: () => {}
},
sourceApplication: {
type: String,
default: null
}
},
data() {
return {
expand: true
expand: true,
defaultFilters: [{ targetKey: 'speckle_type', name: 'Speckle Type' }],
revitFilters: ['type', 'family', 'level'],
allFilters: [],
activeFilter: null,
filterSearch: null
}
},
computed: {}
computed: {
matchingFilters() {
if (this.filterSearch === null) return this.allFilters
else {
return this.allFilters.filter(
(f) =>
f.name.toLowerCase().includes(this.filterSearch.toLowerCase()) ||
f.targetKey.toLowerCase().includes(this.filterSearch.toLowerCase())
)
}
}
},
watch: {
props(newVal) {
if (newVal) this.parseAndSetFilters()
}
},
mounted() {
if (this.props) {
this.parseAndSetFilters()
}
},
methods: {
parseAndSetFilters() {
let keys = Object.keys(this.props)
let filters = []
for (let key of keys) {
let filter = {}
// Handle revit params
if (key.startsWith('parameters.')) {
if (key.endsWith('.value')) {
filter.name = this.props[key.replace('.value', '.name')].allValues[0]
filter.targetKey = key
filter.data = this.props[key]
filters.push(filter)
continue
} else {
continue
}
}
// Beautify level name
if (key === 'level.name') {
filter.name = 'Level'
filter.targetKey = key
filter.data = this.props[key]
filters.push(filter)
continue
}
// Beautify speckle type
if (key === 'speckle_type') {
filter.name = 'Object Type'
filter.targetKey = key
filter.data = this.props[key]
filters.push(filter)
continue
}
// Skip some
if (
key.endsWith('.units') ||
key.endsWith('.speckle_type') ||
key.includes('.parameters.') ||
key.includes('level.') ||
key.includes('renderMaterial') ||
key.includes('.domain') ||
key.includes('plane.') ||
key.includes('baseLine') ||
key.includes('referenceLine') ||
key.includes('end.') ||
key.includes('start.') ||
key.includes('endPoint.') ||
key.includes('midPoint.') ||
key.includes('startPoint.') ||
key.includes('startPoint.') ||
key.includes('displayStyle') ||
key.includes('displayValue') ||
key.includes('displayMesh')
) {
continue
}
filter.name = key
filter.targetKey = key
filter.data = this.props[key]
filters.push(filter)
}
console.log(filters)
this.allFilters = filters
}
}
}
</script>
<style scoped>
.list-overlay {
background: rgba(120, 120, 120, 0.09);
.list-overlay-dark {
background: rgba(40, 40, 40, 1);
z-index: 5;
}
.list-overlay-light {
background: rgba(235, 235, 235, 1);
z-index: 5;
}
.ps {
height: 50vh;
}
</style>
@@ -2,12 +2,13 @@
<v-card class="transparent elevation-5 rounded-md overflow-hidden d-inline-block">
<v-btn
v-show="showVisReset"
v-tooltip="`Reset visibility`"
v-tooltip="`Resets all applied filters`"
tile
small
@click="resetVisibility()"
>
<v-icon small>mdi-filter-off-outline</v-icon>
<!-- <v-icon small>mdi-filter-off-outline</v-icon> -->
Show All
</v-btn>
<v-btn
v-tooltip="`Toggle between perspective or ortho camera.`"
@@ -4,7 +4,7 @@
<v-card
class="space-grotesk primary--text text-h6 pt-5 mb-2 px-5 elevation-0"
:class="`grey ${$vuetify.theme.dark ? 'darken-4' : 'lighten-4'}`"
style="position: sticky; top: 0; z-index: 10; width: 99%"
style="position: sticky; top: 0; z-index: 6; width: 100%"
>
<router-link to="/" class="text-decoration-none">
<v-img
@@ -6,7 +6,6 @@
</portal>
<portal to="nav">
<v-list v-if="stream" nav dense class="mt-0 pt-0">
<v-list-item
link
@@ -33,9 +32,9 @@
:stream-id="stream.id"
/>
<views-display :views="views" />
<views-display v-if="views.length !== 0" :views="views" />
<filters />
<filters :props="objectProperties" :source-application="stream.commit.sourceApplication" />
</portal>
<div style="height: 100vh; width: 100%; top: -64px; position: absolute">
@@ -1,10 +1,5 @@
<template>
<div class="ml-2">
<!-- <v-breadcrumbs large :items="navItems">
<template #divider>
<v-icon>mdi-chevron-right</v-icon>
</template>
</v-breadcrumbs> -->
<div v-if="true">
/
<router-link