Skip to content

Commit

Permalink
feat(ui): improve table and flesh out filter other syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
mesqueeb committed Jul 20, 2023
1 parent 3c52c4d commit 173a823
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 141 deletions.
1 change: 0 additions & 1 deletion packages/ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ const example = ref<'stream' | 'fetch' | 'table'>('table')
font-family: Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
text-align: center
margin-top: 60px
</style>
143 changes: 73 additions & 70 deletions packages/ui/src/components/MagnetarTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import {
} from '../types'
import {
calcCollection,
carbonCopyMap,
columnsToInitialOrderByState,
filterStateToClauses,
filtersToInitialState,
orderByStateToClauses,
splitOnLink,
} from '../utils'
import LoadingSpinner from './LoadingSpinner.vue'
import TableFilter from './TableFilter.vue'
import TableTd from './TableTd.vue'
import TableTh from './TableTh.vue'
import TextWithAnchorTags from './TextWithAnchorTags.vue'
const props = defineProps<{
collection: CollectionInstance<any>
Expand All @@ -41,12 +42,30 @@ function muiLabel(label: MUILabel): string {
// const emit = defineEmits<{}>()
const fetchState = ref<'ok' | 'end' | 'fetching' | { error: string }>('ok')
const filterState = ref<FilterState>(filtersToInitialState(props.filters))
const orderByState = ref<OrderByState>(columnsToInitialOrderByState(props.columns))
const initialFilterState: FilterState = filtersToInitialState(props.filters)
const initialOrderByState: OrderByState = columnsToInitialOrderByState(props.columns)
const filterState = ref<FilterState>(carbonCopyMap(initialFilterState))
const orderByState = ref<OrderByState>(carbonCopyMap(initialOrderByState))
const currentFilters = computed(() => filterStateToClauses(filterState.value))
const currentOrderBy = computed(() => orderByStateToClauses(orderByState.value))
const initialStateActive = computed<boolean>(
() =>
filterState.value.size === initialFilterState.size &&
orderByState.value.size === initialOrderByState.size &&
[...filterState.value.entries()].every(
([key, value]) => initialFilterState.get(key) === value
) &&
[...orderByState.value.entries()].every(
([key, value]) => initialOrderByState.get(key) === value
)
)
const hasSomeFilterOrOrderby = computed<boolean>(
() => !!filterState.value.size || !!orderByState.value.size
)
/** This instance is not computed so that we can delay setting it after fetching the relevant records */
const collectionInstance = ref(
calcCollection(props.collection, filterState.value, orderByState.value)
Expand All @@ -58,8 +77,8 @@ function clearAllRecords(): void {
}
function resetState(): void {
filterState.value = filtersToInitialState(props.filters)
orderByState.value = columnsToInitialOrderByState(props.columns)
filterState.value = carbonCopyMap(initialFilterState)
orderByState.value = carbonCopyMap(initialOrderByState)
clearAllRecords()
fetchMore()
}
Expand Down Expand Up @@ -145,8 +164,8 @@ async function setOrderBy(
</script>

<template>
<div class="magnetar-table">
<section class="magnetar-table-info">
<div class="magnetar-table magnetar-column magnetar-gap-md">
<section class="magnetar-row magnetar-gap-md">
<table>
<thead>
<tr>
Expand All @@ -167,7 +186,7 @@ async function setOrderBy(
<LoadingSpinner v-if="fetchState === 'fetching'" class="magnetar-fetch-state-loading" />
</section>

<section class="magnetar-table-filters">
<section class="magnetar-row magnetar-gap-md">
<TableFilter
v-for="filter in filters"
:filter="filter"
Expand All @@ -176,9 +195,9 @@ async function setOrderBy(
:parseLabel="parseLabel"
@setFilter="([where, to]) => setFilter(where, to)"
/>
<div v-if="currentFilters.length || currentOrderBy.length" class="magnetar-current-state">
<div v-if="hasSomeFilterOrOrderby" class="magnetar-column magnetar-gap-sm">
<h6>{{ muiLabel('magnetar table active filters') }}</h6>
<div>
<div class="magnetar-row magnetar-gap-sm magnetar-active-filters">
<div v-for="_filter in currentFilters" :key="JSON.stringify(_filter)">
<!-- TODO: @click="() => filterState.delete(_filter)" -->
.where({{ _filter.map((f) => JSON.stringify(f)).join(', ') }})
Expand All @@ -188,53 +207,50 @@ async function setOrderBy(
.orderBy({{ _orderBy.map((o) => JSON.stringify(o)).join(', ') }})
</div>
</div>
<div v-if="isPlainObject(fetchState)" class="magnetar-fetch-state-error">
<template v-for="(part, i) of splitOnLink(fetchState.error)" :key="i">
<span v-if="part.kind === 'text'">
<template v-for="(line, _i) of part.content.split(/[\n\r]/)" :key="_i"
><br v-if="_i !== 0" />{{ line }}</template
>
</span>
<a v-if="part.kind === 'link'" :href="part.content" target="_blank">{{
part.content
}}</a>
</template>
</div>
<div>
<button @click="() => resetState()">
{{ muiLabel('magnetar table fetch-state reset') }}
</button>
<button @click="() => clearState()">
{{ muiLabel('magnetar table clear filters button') }}
</button>
</div>
</div>
</section>

<TextWithAnchorTags
v-if="isPlainObject(fetchState)"
class="magnetar-fetch-state-error"
:text="fetchState.error"
/>

<section
v-if="hasSomeFilterOrOrderby || !initialStateActive"
class="magnetar-row magnetar-gap-sm"
>
<button v-if="!initialStateActive" @click="() => resetState()">
{{ muiLabel('magnetar table fetch-state reset') }}
</button>
<button v-if="hasSomeFilterOrOrderby" @click="() => clearState()">
{{ muiLabel('magnetar table clear filters button') }}
</button>
</section>

<table>
<thead>
<tr>
<TableTh
v-for="(column, i) in columns"
:key="column.fieldPath + 'th' + i"
:column="column"
:orderByState="orderByState"
:parseLabel="parseLabel"
@setOrderBy="([fieldPath, direction]) => setOrderBy(fieldPath, direction)"
/>
<th v-for="(column, i) in columns" :key="column.fieldPath + 'th' + i">
<TableTh
:column="column"
:orderByState="orderByState"
:parseLabel="parseLabel"
@setOrderBy="([fieldPath, direction]) => setOrderBy(fieldPath, direction)"
/>
</th>
</tr>
</thead>

<tbody>
<tr v-for="row in rows" :key="row.id">
<template
<td
v-for="(column, columnIndex) in columns"
:key="(column.fieldPath || column.slot) + 'td' + row.id"
>
<slot :name="column.slot || columnIndex" v-bind="{ data: row }">
<TableTd :row="row" :column="column" :parseLabel="parseLabel"> </TableTd>
</slot>
</template>
</td>
</tr>
<slot v-if="!rows.length" name="empty">
<tr>
Expand All @@ -259,41 +275,28 @@ async function setOrderBy(
</template>

<style lang="sass" scoped>
.magnetar-table
display: flex
flex-direction: column
gap: 1rem
.magnetar-table-info
.magnetar-row
display: flex
flex-direction: row
flex-wrap: wrap
gap: 1rem
align-items: center
min-height: 26px
h6
margin: 0
.magnetar-fetch-state-error
white-space: pre-line
word-break: break-word
text-align: left
color: var(--c-error, indianred)
.magnetar-current-state
.magnetar-column
display: flex
flex-direction: column
align-items: flex-start
.magnetar-gap-sm
gap: 0.5rem
> h6
.magnetar-gap-md
gap: 1rem
.magnetar-table
h6
margin: 0
.magnetar-table-info
min-height: 26px
.magnetar-fetch-state-error
color: var(--c-error, indianred)
.magnetar-active-filters
> div
display: flex
flex-wrap: wrap
gap: .5rem
align-items: center
> div
background: var(--c-primary-extra-light, whitesmoke)
border-radius: 0.25rem
padding: 0.25rem 0.5rem
.magnetar-table-filters
display: flex
flex-wrap: wrap
gap: 1rem
background: var(--c-primary-extra-light, whitesmoke)
border-radius: 0.25rem
padding: 0.25rem 0.5rem
</style>
17 changes: 10 additions & 7 deletions packages/ui/src/components/TableFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ const filterLabel = computed<string>(() => {
</script>

<template>
<fieldset class="magnetar-table-filter">
<fieldset class="magnetar-table-filter" :class="filter.class" :style="filter.style">
<legend>{{ filterLabel }}</legend>
<template v-if="filter.type === 'checkboxes'">
<template v-for="option in filter.options">
<div v-for="option in filter.options" class="magnetar-inline-block">
<input
:id="JSON.stringify(option.where)"
type="checkbox"
Expand All @@ -62,10 +62,10 @@ const filterLabel = computed<string>(() => {
>{{ parseLabel ? parseLabel(option.label) : option.label }}
<small> ({{ props.collection.where(...option.where).count }})</small></label
>
</template>
</div>
</template>
<template v-if="filter.type === 'radio'">
<template v-for="option in filter.options">
<div v-for="option in filter.options" class="magnetar-inline-block">
<input
:id="JSON.stringify(option.where)"
type="radio"
Expand All @@ -78,7 +78,7 @@ const filterLabel = computed<string>(() => {
>{{ parseLabel ? parseLabel(option.label) : option.label }}
<small> ({{ props.collection.where(...option.where).count }})</small></label
>
</template>
</div>
</template>
<template v-if="filter.type === 'select'">
<select v-model="selectModel">
Expand All @@ -94,6 +94,9 @@ const filterLabel = computed<string>(() => {
<style lang="sass" scoped>
.magnetar-table-filter
> label
margin: 0.25rem
border: thin solid
.magnetar-inline-block
display: inline-block
> label
margin: 0.25rem
</style>
31 changes: 14 additions & 17 deletions packages/ui/src/components/TableTd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ const cellValueRaw = computed<any>(() => {
})
const cellValueParsed = computed<string>(() => {
const { parseValue, parseMarkdown } = props.column
const { parseValue } = props.column
const rawValue = cellValueRaw.value
const parsed = parseValue ? parseValue({ value: rawValue, data: props.row }) : rawValue
return parseMarkdown ? parseMarkdown(parsed) : parsed
return parseValue ? parseValue({ value: rawValue, data: props.row }) : rawValue
})
const cellAttrs = computed<{ class: string | undefined; style: string | undefined }>(() => {
Expand Down Expand Up @@ -71,20 +70,18 @@ async function handleClick(index: number): Promise<void> {
</script>

<template>
<td class="magnetar-table-td">
<div :class="cellAttrs.class" :style="cellAttrs.style">
<div v-if="column.parseMarkdown" v-html="cellValueParsed" />
<div v-if="!column.parseMarkdown">{{ cellValueParsed }}</div>
<button
v-for="(button, i) in buttonAttrArr"
:key="button?.label"
:disabled="button.disabled || undefined"
@click.stop="() => handleClick(i)"
>
{{ button.label }}
</button>
</div>
</td>
<div :class="cellAttrs.class" :style="cellAttrs.style">
<div v-if="column.html" v-html="cellValueParsed" />
<div v-if="!column.html">{{ cellValueParsed }}</div>
<button
v-for="(button, i) in buttonAttrArr"
:key="button?.label"
:disabled="button.disabled || undefined"
@click.stop="() => handleClick(i)"
>
{{ button.label }}
</button>
</div>
</template>
<style scoped></style>
17 changes: 7 additions & 10 deletions packages/ui/src/components/TableTh.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,17 @@ const label = computed<string>(() => {
</script>

<template>
<th :class="`magnetar-table-th _direction-${direction}`" @click="(e) => onClick(e)">
<div>
<div>{{ label }}</div>
<i v-if="direction !== 'unsortable'" class="_sort-arrows"></i>
</div>
</th>
<div :class="`magnetar-table-th _direction-${direction}`" @click="(e) => onClick(e)">
<div>{{ label }}</div>
<i v-if="direction !== 'unsortable'" class="_sort-arrows"></i>
</div>
</template>

<style scoped lang="sass">
.magnetar-table-th
> div
display: flex
gap: 0.25rem
align-items: center
display: flex
gap: 0.25rem
align-items: center
._sort-arrows
float: right
box-sizing: border-box
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/TestTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const columns: MUIColumn<Item>[] = [
fieldPath: 'id',
buttons: [{ label: 'Copy', handler: ({ value }) => alert(`copied to clipboard ${value}`) }],
},
{ label: 'Title', fieldPath: 'title', sortable: true },
{ label: 'Title', fieldPath: 'title', sortable: { orderBy: 'asc', position: 0 } },
{ label: 'Custom Slot', slot: 'somecolumn' },
{
label: 'Is it done?',
Expand Down
Loading

0 comments on commit 173a823

Please sign in to comment.