Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@
--default-ring-color: var(--color-indigo-500);
}

@layer utilities {
.animate-pulse {
animation: pulse 1.25s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
}

[class*='group/report']:has(.tile-tabs.phx-hook-loading)
[class*='group-has-[.tile-tabs.phx-hook-loading]/report:animate-pulse'] {
animation: pulse 1.25s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
}

[class*='group/dashboard'].phx-navigation-loading
[class*='group-[.phx-navigation-loading]/dashboard:animate-pulse'] {
animation: pulse 1.25s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
}
}

@media print {
canvas {
width: 100% !important;
Expand Down
7 changes: 1 addition & 6 deletions assets/js/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react'
import { LiveViewPortal } from './components/liveview-portal'
import VisitorGraph from './stats/graph/visitor-graph'
import Sources from './stats/sources'
import Pages from './stats/pages'
Expand All @@ -8,11 +7,9 @@ import Devices from './stats/devices'
import { TopBar } from './nav-menu/top-bar'
import Behaviours from './stats/behaviours'
import { useQueryContext } from './query-context'
import { useSiteContext } from './site-context'
import { hasConversionGoalFilter, isRealTimeDashboard } from './util/filters'
import { isRealTimeDashboard } from './util/filters'
import { useAppNavigate } from './navigation/use-app-navigate'
import { parseSearch } from './util/url-search-params'
import { getDomainScopedStorageKey } from './util/storage'

function DashboardStats({
importedDataInView,
Expand All @@ -22,8 +19,6 @@ function DashboardStats({
updateImportedDataInView?: (v: boolean) => void
}) {
const navigate = useAppNavigate()
const site = useSiteContext()
const { query } = useQueryContext()

// Handler for navigation events delegated from LiveView dashboard.
// Necessary to emulate navigation events in LiveView with pushState
Expand Down
6 changes: 3 additions & 3 deletions assets/js/liveview/datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ export default buildHook({
this.addListener('click', this.el, (e) => {
if (this.dates.length) {
const button = e.target.closest('button')

let updated = false

if (button === this.prevPeriodButton) {
updated = prevPeriod.bind(this)()
}

if (button === this.nextPeriodButton) {
nextPeriod.bind(this)()
updated = nextPeriod.bind(this)()
Expand All @@ -70,7 +70,7 @@ export default buildHook({
if (updated) {
this.debouncedPushEvent()
}

this.periodLabel.innerText = this.labels[this.currentIndex]
this.prevPeriodButton.dataset.disabled = `${this.currentIndex == 0}`
this.nextPeriodButton.dataset.disabled = `${this.currentIndex == this.dates.length - 1}`
Expand Down
12 changes: 12 additions & 0 deletions assets/js/liveview/live_socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ if (csrfToken && websocketUrl) {
window.addEventListener('phx:page-loading-start', (_info) => topbar.show())
window.addEventListener('phx:page-loading-stop', (_info) => topbar.hide())

const dashboardContainer = document.getElementById('live-dashboard-container')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's nice 👍 Might help simplify loading state selectors I'm currently using in my branch.

if (dashboardContainer) {
window.addEventListener('phx:page-loading-start', (info) => {
if (info.detail?.kind === 'patch') {
dashboardContainer.classList.add('phx-navigation-loading')
}
})
window.addEventListener('phx:page-loading-stop', () => {
dashboardContainer.classList.remove('phx-navigation-loading')
})
}

liveSocket.connect()
window.liveSocket = liveSocket
}
2 changes: 1 addition & 1 deletion lib/plausible_web/live/components/dashboard/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ defmodule PlausibleWeb.Components.Dashboard.Base do
~H"""
<div class="w-full h-full relative" style={@style}>
<div
class={"absolute top-0 left-0 h-full rounded-sm transition-colors duration-150 #{@background_class || ""}"}
class={"absolute top-0 left-0 h-full rounded-sm transition-[width] duration-200 ease-in-out #{@background_class || ""}"}
data-test-id="bar-indicator"
style={"width: #{@width_percent}%"}
>
Expand Down
162 changes: 123 additions & 39 deletions lib/plausible_web/live/components/dashboard/report_list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ defmodule PlausibleWeb.Components.Dashboard.ReportList do

use PlausibleWeb, :component

alias PlausibleWeb.Components.Dashboard.Base
alias PlausibleWeb.Components.Dashboard.Metric
alias Plausible.Stats.QueryResult
alias PlausibleWeb.Components.Dashboard.{Base, Metric}
alias Plausible.Stats.{QueryResult, ParsedQueryParams}
alias Plausible.Stats.Dashboard.Utils

@max_items 9
Expand All @@ -19,6 +18,15 @@ defmodule PlausibleWeb.Components.Dashboard.ReportList do

def height, do: @min_height

attr :site, Plausible.Site, required: true
attr :id, :string, required: true
attr :params, ParsedQueryParams, required: true
attr :connected?, :boolean, required: true
attr :dimension, :string, required: true
attr :key_label, :string, required: true
attr :query_result, QueryResult, required: true
attr :external_link_fn, :any, default: nil

def report(assigns) do
assigns =
assign(assigns,
Expand All @@ -30,44 +38,120 @@ defmodule PlausibleWeb.Components.Dashboard.ReportList do
col_min_width: @col_min_width
)

%QueryResult{results: results, meta: meta, query: query} = assigns.query_result
if !assigns.connected? do
~H"""
<.skeleton
id={"#{@id}-skeleton"}
min_height={@min_height}
row_height={@row_height}
row_gap_height={@row_gap_height}
data_container_height={@data_container_height}
col_min_width={@col_min_width}
max_items={@max_items}
/>
"""
else
%QueryResult{results: results, meta: meta, query: query} = assigns.query_result

assigns =
assign(assigns,
results: results,
metric_keys: query[:metrics],
metric_labels: meta[:metric_labels],
empty?: Enum.empty?(results)
)

~H"""
<.no_data :if={@empty?} min_height={@min_height} id={"#{@id}-no-data"} />

<div
:if={not @empty?}
id={@id}
class="h-full flex flex-col group-has-[.tile-tabs.phx-hook-loading]/report:opacity-60 group-[.phx-navigation-loading]/dashboard:opacity-60"
>
<div
class="group-has-[.tile-tabs.phx-hook-loading]/report:animate-pulse group-[.phx-navigation-loading]/dashboard:animate-pulse"
style={"min-height: #{@row_height}px;"}
>
<.report_header
key_label={@key_label}
metric_labels={@metric_labels}
col_min_width={@col_min_width}
/>
</div>

<div
class="grow group-has-[.tile-tabs.phx-hook-loading]/report:animate-pulse group-[.phx-navigation-loading]/dashboard:animate-pulse"
style={"min-height: #{@data_container_height}px;"}
>
<.report_row
:for={{item, item_index} <- Enum.with_index(@results)}
link_fn={assigns[:external_link_fn]}
item={item}
item_index={item_index}
item_name={List.first(item.dimensions)}
metrics={Enum.zip(@metric_keys, item.metrics)}
bar_max_value={bar_max_value(@results, @metric_keys)}
site={@site}
params={@params}
dimension={@dimension}
row_height={@row_height}
row_gap_height={@row_gap_height}
col_min_width={@col_min_width}
/>
</div>
</div>
"""
end
end

defp skeleton(assigns) do
assigns =
assign(assigns,
results: results,
metric_keys: query[:metrics],
metric_labels: meta[:metric_labels],
empty?: Enum.empty?(results)
)
assigns
|> assign(:bar_widths, [100, 45, 25, 14, 10, 7, 5, 4, 3])
|> assign(:number_widths, [9, 8, 7, 8, 9, 7, 9, 7, 8])
|> assign(:value_widths, [22, 16, 20, 14, 19, 15, 21, 13, 17])

~H"""
<.no_data :if={@empty?} min_height={@min_height} data_test_id={@data_test_id} />

<div :if={not @empty?} class="h-full flex flex-col" data-test-id={@data_test_id}>
<div style={"min-height: #{@row_height}px;"}>
<.report_header
key_label={@key_label}
metric_labels={@metric_labels}
col_min_width={@col_min_width}
/>
<div
id={@id}
class="h-full flex flex-col"
style={"min-height: #{@min_height}px;"}
>
<div
class="flex justify-between w-full pt-4"
style={"height: #{@row_height}px;"}
>
<div class="w-12 h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse"></div>
</div>

<div class="grow" style={"min-height: #{@data_container_height}px;"}>
<.report_row
:for={{item, item_index} <- Enum.with_index(@results)}
link_fn={assigns[:external_link_fn]}
item={item}
item_index={item_index}
item_name={List.first(item.dimensions)}
metrics={Enum.zip(@metric_keys, item.metrics)}
bar_max_value={bar_max_value(@results, @metric_keys)}
site={@site}
params={@params}
dimension={@dimension}
row_height={@row_height}
row_gap_height={@row_gap_height}
col_min_width={@col_min_width}
/>
<div
:for={
{bar_width, number_width, value_width} <-
Enum.zip([@bar_widths, @number_widths, @value_widths])
}
class="flex items-center justify-between w-full"
style={"margin-top: #{@row_gap_height}px;"}
>
<div
class="bg-gray-100/70 dark:bg-gray-800/70 rounded-sm animate-pulse relative"
style={"height: #{@row_height}px; width: #{bar_width}%;"}
>
<div
class="h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse absolute top-1/2 -translate-y-1/2 left-2"
style={"width: #{value_width * 4}px;"}
>
</div>
</div>
<div
class="flex items-center justify-end"
style={"height: #{@row_height}px; width: 70px;"}
>
<div
class="h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse"
style={"width: #{number_width * 4}px;"}
>
</div>
</div>
</div>
</div>
"""
Expand All @@ -76,8 +160,8 @@ defmodule PlausibleWeb.Components.Dashboard.ReportList do
defp no_data(assigns) do
~H"""
<div
data-test-id={@data_test_id}
class="w-full h-full flex flex-col justify-center group-has-[.tile-tabs.phx-hook-loading]:hidden"
id={@id}
class="w-full h-full flex flex-col justify-center group-has-[.tile-tabs.phx-hook-loading]/report:hidden"
style={"min-height: #{@min_height}px;"}
>
<div class="mx-auto font-medium text-gray-500 dark:text-gray-400">
Expand Down Expand Up @@ -149,7 +233,7 @@ defmodule PlausibleWeb.Components.Dashboard.ReportList do
>
<div class="flex justify-start items-center gap-x-1.5 px-2 py-1.5 text-sm dark:text-gray-300 relative z-9 break-all w-full">
<Base.dashboard_link
class="max-w-max w-full flex items-center md:overflow-hidden hover:underline"
class="block min-w-0 truncate hover:underline"
to={Utils.dashboard_route(@site, @params, filter: [:is, @dimension, [@item_name]])}
>
{trim_name(@item_name, @col_min_width)}
Expand Down
31 changes: 12 additions & 19 deletions lib/plausible_web/live/components/dashboard/tile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule PlausibleWeb.Components.Dashboard.Tile do
<div
data-tile
id={@id}
class="relative min-h-[430px] w-full p-5 flex flex-col bg-white dark:bg-gray-900 shadow-sm rounded-md md:min-h-initial md:h-27.25rem"
class="relative min-h-[430px] w-full p-5 flex flex-col bg-white border border-gray-150 dark:bg-gray-900 dark:border-gray-800 rounded-lg md:min-h-initial md:h-27.25rem"
>
<%!-- reportheader --%>
<div class="w-full flex justify-between border-b border-gray-200 dark:border-gray-750">
Expand All @@ -37,28 +37,14 @@ defmodule PlausibleWeb.Components.Dashboard.Tile do
>
{render_slot(@tabs)}
</div>
<div class="group-[.phx-navigation-loading]:hidden group-has-[.tile-tabs.phx-hook-loading]:hidden">
<div class="group-[.phx-navigation-loading]/dashboard:hidden group-has-[.tile-tabs.phx-hook-loading]/report:hidden">
{render_slot(@warnings)}
</div>
</div>
<.details_link details_route={@details_route} path="/pages" />
<.details_link :if={@connected?} details_route={@details_route} path="/pages" />
</div>
<%!-- reportbody --%>
<div
class={"w-full flex-col justify-center #{if @connected?, do: "group-[.phx-navigation-loading]:flex group-has-[.tile-tabs.phx-hook-loading]:flex hidden", else: "flex"}"}
style={"min-height: #{@height}px;"}
>
<div class="mx-auto loading">
<div></div>
</div>
</div>

<div
:if={@connected?}
class="group-[.phx-navigation-loading]:hidden group-has-[.tile-tabs.phx-hook-loading]:hidden"
>
{render_slot(@inner_block)}
</div>
{render_slot(@inner_block)}
</div>
"""
end
Expand All @@ -68,6 +54,7 @@ defmodule PlausibleWeb.Components.Dashboard.Tile do
attr :active_tab, :string, required: true
attr :storage_key, :string, required: true
attr :target, :any, required: true
attr :connected?, :boolean, default: true

def tab(assigns) do
assigns =
Expand All @@ -83,9 +70,10 @@ defmodule PlausibleWeb.Components.Dashboard.Tile do
~H"""
<div
{@data_attrs}
class="-mb-px pb-4 data-[active=true]:border-b-2 data-[active=true]:border-gray-900 data-[active=true]:dark:border-gray-100"
class={"-mb-px pb-4 #{if @connected?, do: "data-[active=true]:border-b-2 data-[active=true]:border-gray-900 data-[active=true]:dark:border-gray-100"}"}
>
<button
:if={@connected?}
class="group/tab flex rounded-sm"
data-tab-key={@tab_key}
data-report-label={@report_label}
Expand All @@ -107,6 +95,11 @@ defmodule PlausibleWeb.Components.Dashboard.Tile do
{@report_label}
</span>
</button>
<div
:if={!@connected?}
class="h-3.5 mb-1 w-18 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse"
>
</div>
</div>
"""
end
Expand Down
Loading
Loading