graphs and fancy comments

This commit is contained in:
Luna D. 2024-05-31 20:07:00 +02:00
parent ab30673208
commit a3c6cba47e
No known key found for this signature in database
GPG key ID: 4B1C63448394F688
18 changed files with 208 additions and 76 deletions

View file

@ -4,6 +4,7 @@ $narrow-layout-width: 960px;
$medium-layout-width: $min-desktop-width;
$font-family-base: verdana, arial, helvetica, sans-serif;
$font-family-monospace: "Droid Sans Mono", monospace;
$font-family-heading: $font-family-base;
:root {
/* Paddings */
@ -84,6 +85,7 @@ $font-family-monospace: "Droid Sans Mono", monospace;
/* Fonts */
--font-family: $font-family-base;
--font-family-monospace: $font-family-monospace;
--font-family-heading: $font-family-heading;
--font-size: 14px;
--font-tiny-size: 12px;
--font-micro-size: 10px;

View file

@ -150,6 +150,10 @@
border-radius: var(--border-radius-inner);
}
.button--transparent {
background: 0 !important;
}
@mixin button-type primary;
@mixin button-type secondary;
@mixin button-type success;

View file

@ -91,6 +91,11 @@
flex: 1 0 auto;
}
.flex__break {
flex-basis: 100%;
width: 0;
}
@mixin if-desktop {
.flex--maybe-wrap {
flex-wrap: nowrap;

View file

@ -1,8 +1,8 @@
@define-mixin heading $el {
$(el) {
font-family: var(--font-family-heading);
font-size: var(--font-$(el)-size);
font-weight: bold;
font-variant: small-caps;
font-weight: normal;
padding: var(--padding-small) 0;
margin-top: 0;
border-bottom: 1px solid var(--primary-border-color);
@ -30,12 +30,6 @@
@mixin heading h4;
@mixin heading h5;
@mixin heading h6;
/* To differentiate between h5 and h6, simply make h6 not bold */
h6 {
font-weight: normal;
}
@mixin heading-type success;
@mixin heading-type warning;
@mixin heading-type danger;

View file

@ -11,6 +11,7 @@
.column-layout__main {
flex: 1 0 auto;
max-width: calc(100% - var(--column-left-width) - var(--padding-normal));
}
@mixin if-mobile {
@ -21,6 +22,10 @@
.column-layout__left {
margin-right: 0;
}
.column-layout__main {
max-width: 100%;
}
}
#container {

View file

@ -0,0 +1,5 @@
:root {
--font-family: sans-serif;
--font-family-monospace: monospace;
--font-family-heading: sans-serif;
}

View file

@ -80,6 +80,11 @@
padding: var(--padding-small);
}
.communication__options {
display: flex;
flex-flow: row;
}
.communication__options__staff {
display: flex;
align-items: center;
@ -87,19 +92,40 @@
margin-top: var(--padding-small);
}
.communication__options__post-time {
.communication__post-time {
display: flex;
align-items: center;
gap: var(--padding-small);
flex-wrap: wrap;
padding: var(--padding-small);
flex-wrap: nowrap;
}
.communication__post-time > time,
.communication__options__edit-time {
width: 100%;
margin-bottom: var(--padding-small);
white-space: nowrap;
}
@mixin if-mobile {
.communication__options {
flex-flow: column;
}
.communication__options__edit-time {
width: 100%;
}
.communication__post-time > time,
.communication__options__edit-time {
white-space: normal;
}
.communication__post-time {
border-radius: var(--border-radius-inner);
margin-top: var(--padding-small);
background: var(--primary-muted-color);
flex-wrap: wrap;
}
.communication__body__text {
margin-top: var(--padding-normal);
}

View file

@ -22,13 +22,33 @@
.sparkline {
border-bottom: 1px solid var(--primary-border-color);
display: flex;
height: var(--padding-normal);
height: var(--padding-large);
padding: var(--padding-tiny);
overflow: hidden;
}
.sparkline svg {
overflow: visible;
}
.barline__bar {
fill: var(--primary-border-color);
fill: var(--primary-link-color);
}
.barline__bar:hover {
fill: var(--danger-color);
}
.barline__dot {
fill: var(--primary-border-color);
stroke: var(--link-color);
stroke-width: 1px;
}
.barline__dot:hover {
fill: var(--danger-color);
}
.barline__dot[cy="21.25"] {
display: none;
}

30
assets/js/graph.ts Normal file
View file

@ -0,0 +1,30 @@
/**
* Graph Logic
*
* Scales graphs, makes graphs resizable, and stuff like that
*/
import { $, $$ } from './utils/dom';
function resizeGraphs() {
$$<SVGSVGElement>('#js-sparkline-svg').forEach(el => {
const parent: HTMLElement | null = el.parentElement;
if (parent) {
el.viewBox.baseVal.width = parent.clientWidth;
const graph: SVGPathElement | null = $<SVGPathElement>('#js-barline-graph', el);
if (graph) {
graph.setAttribute('style', `transform: scale(${parent.clientWidth / 375});`);
}
}
});
}
function sizeGraphs() {
resizeGraphs();
window.addEventListener('resize', resizeGraphs);
}
export { sizeGraphs };

View file

@ -35,6 +35,7 @@ import { hideStaffTools } from './staffhider';
import { pollOptionCreator } from './poll';
import { warnAboutPMs } from './pmwarning';
import { imageSourcesCreator } from './sources';
import { sizeGraphs } from './graph';
whenReady(() => {
@ -68,5 +69,6 @@ whenReady(() => {
pollOptionCreator();
warnAboutPMs();
imageSourcesCreator();
sizeGraphs();
});

View file

@ -1,3 +1,5 @@
- options = render PhilomenaWeb.CommentView, "_comment_link.html", comment: @comment, conn: @conn
article.block.communication id="comment_#{@comment.id}"
= if not @comment.approved and not @comment.hidden_from_users and (can?(@conn, :hide, @comment) or @comment.user_id == @conn.assigns.current_user.id) do
.block__content
@ -22,13 +24,14 @@ article.block.communication id="comment_#{@comment.id}"
= submit "Delete", class: "button"
.block__content.flex.flex--no-wrap class=communication_body_class(@comment)
= render PhilomenaWeb.CommunicationView, "_body.html", object: @comment, body: @body, conn: @conn, name: "comment"
= render PhilomenaWeb.CommunicationView, "_body.html", object: @comment, body: @body, conn: @conn, name: "comment", options: options
.block__content.communication__options
.flex.flex--wrap.flex--spaced-out
= render PhilomenaWeb.CommentView, "_comment_options.html", comment: @comment, conn: @conn
= if can?(@conn, :hide, @comment) do
.flex__spacer
.js-staff-action
.communication__options__staff
= cond do
@ -43,16 +46,16 @@ article.block.communication id="comment_#{@comment.id}"
' Delete Contents
- not @comment.hidden_from_users and not @comment.destroyed_content ->
a.button.button--danger.togglable-delete-form-link href="#" data-click-toggle="#inline-del-form-comment-#{@comment.id}"
a.button.button--danger.button--transparent.togglable-delete-form-link href="#" data-click-toggle="#inline-del-form-comment-#{@comment.id}"
i.fas.fa-times>
' Delete
- true ->
= if can?(@conn, :show, :ip_address) do
.button.button--information.js-staff-action
.button.button--warning.button--transparent.js-staff-action
=<> link_to_ip(@conn, @comment.ip)
.button.button--information.js-staff-action
.button.button--warning.button--transparent.js-staff-action
=<> link_to_fingerprint(@conn, @comment.fingerprint)
= form_for :comment, Routes.image_comment_hide_path(@conn, :create, @comment.image_id, @comment), [class: "togglable-delete-form hidden flex", id: "inline-del-form-comment-#{@comment.id}"], fn f ->

View file

@ -0,0 +1,18 @@
- comment_link = Routes.image_path(@conn, :show, @comment.image) <> "#comment_#{@comment.id}"
.flex__spacer.hidden--desktop
a.communication__interaction.hidden--desktop title="Link to comment" href=comment_link
i.fa.fa-link
= if not is_nil(@comment.edited_at) and can?(@conn, :show, @comment) do
a.communication__options__edit-time href=Routes.image_comment_history_path(@conn, :index, @comment.image, @comment)
span.hidden--mobile> - edited
span.hidden--desktop> Edited
=> pretty_time(@comment.edited_at)
= if @comment.edit_reason not in [nil, ""] do
| (
= @comment.edit_reason
| )
.flex__spacer.hidden--mobile
a.communication__interaction.hidden--mobile title="Link to comment" href=comment_link
i.fa.fa-link

View file

@ -1,29 +1,11 @@
.communication__options__post-time
span
' Posted
=> pretty_time(@comment.created_at)
a.button.button--primary href=Routes.image_comment_report_path(@conn, :new, @comment.image, @comment)
i.fa.fa-flag>
' Report
= if not is_nil(@comment.edited_at) and can?(@conn, :show, @comment) do
a.communication__options__edit-time href=Routes.image_comment_history_path(@conn, :index, @comment.image, @comment)
' Edited
=> pretty_time(@comment.edited_at)
= if @comment.edit_reason not in [nil, ""] do
' because:
=> @comment.edit_reason
.flex.flex--normal-gap.flex--centered
- link_path = Routes.image_path(@conn, :show, @comment.image) <> "#comment_#{@comment.id}"
- safe_author = PhilomenaWeb.PostView.markdown_safe_author(@comment)
- quote_body = if @comment.hidden_from_users, do: "", else: @comment.body
a.button.button--primary title="Link to comment" href=link_path
i.fa.fa-link
' Link
a.button.button--primary href=Routes.image_comment_report_path(@conn, :new, @comment.image, @comment)
i.fa.fa-flag>
' Report
a.button.button--primary.post-reply.post-reply-quote href=link_path data-reply-url=link_path data-author=safe_author data-post=quote_body
i.fa.fa-quote-right

View file

@ -1,6 +1,8 @@
- options = render PhilomenaWeb.CommentView, "_comment_link.html", comment: @comment, conn: @conn
article.block.communication id="comment_#{@comment.id}"
.block__content.flex.flex--no-wrap class=communication_body_class(@comment)
= render PhilomenaWeb.CommunicationView, "_body.html", object: @comment, image: @comment.image, body: @body, conn: @conn, name: "comment"
= render PhilomenaWeb.CommunicationView, "_body.html", object: @comment, image: @comment.image, body: @body, conn: @conn, name: "comment", options: options
.block__content.communication__options
.flex.flex--wrap.flex--spaced-out
@ -15,21 +17,21 @@ article.block.communication id="comment_#{@comment.id}"
' Restore
= if can?(@conn, :delete, @comment) do
= link(to: Routes.image_comment_delete_path(@conn, :create, @comment.image_id, @comment), data: [confirm: "Are you sure?"], method: "post", class: "button button--danger") do
= link(to: Routes.image_comment_delete_path(@conn, :create, @comment.image_id, @comment), data: [confirm: "Are you sure?"], method: "post", class: "button button--danger button--transparent") do
i.fas.fa-times>
' Delete Contents
- not @comment.hidden_from_users and not @comment.destroyed_content ->
a.button.button--danger.togglable-delete-form-link href="#" data-click-toggle="#inline-del-form-comment-#{@comment.id}"
a.button.button--danger.button--transparent.togglable-delete-form-link href="#" data-click-toggle="#inline-del-form-comment-#{@comment.id}"
i.fas.fa-times>
' Delete
- true ->
= if can?(@conn, :show, :ip_address) do
.button.button--warning.js-staff-action
.button.button--warning.button--transparent.js-staff-action
=<> link_to_ip(@conn, @comment.ip)
.button.button--information.js-staff-action
.button.button--warning.button--transparent.js-staff-action
=<> link_to_fingerprint(@conn, @comment.fingerprint)
= form_for :comment, Routes.image_comment_hide_path(@conn, :create, @comment.image_id, @comment), [class: "togglable-delete-form hidden flex", id: "inline-del-form-comment-#{@comment.id}"], fn f ->

View file

@ -1,4 +1,5 @@
- anon = is_nil(assigns[:noanon]) or @noanon == false
- options = if is_nil(assigns[:options]), do: "", else: @options
- avatar = cond do
- not is_nil(assigns[:image]) ->
@ -45,6 +46,11 @@
.communication__sender-block
span.communication__sender-name
= username
.flex__spacer
span.communication__post-time
' Posted
=< pretty_time(@object.created_at)
= options
= title
.communication__body__text
= contents
@ -55,6 +61,10 @@
span.communication__sender-name
= username
= title
span.communication__post-time
' Posted
= pretty_time(@object.created_at)
= options
.communication__body.communication__body__text
= contents

View file

@ -1,3 +1,17 @@
- post_link = Routes.forum_topic_path(@conn, :show, @post.topic.forum, @post.topic, post_id: @post.id) <> "#post_#{@post.id}"
- options = if true do
.flex__spacer.hidden--desktop
a.communication__interaction.hidden--desktop title="Link to post" href=post_link
i.fa.fa-link
= if not is_nil(@post.edited_at) and can?(@conn, :show, @post) do
a href=Routes.forum_topic_post_history_path(@conn, :index, @post.topic.forum, @post.topic, @post)
' Edited
=> pretty_time(@post.edited_at)
= if @post.edit_reason not in [nil, ""] do
' because:
=> @post.edit_reason
article.block.communication id="post_#{@post.id}"
= if not @post.approved and not @post.hidden_from_users and (can?(@conn, :hide, @post) or @post.user_id == @conn.assigns.current_user.id) do
.block__content
@ -22,7 +36,7 @@ article.block.communication id="post_#{@post.id}"
= submit "Delete", class: "button"
.block__content.flex.flex--no-wrap class=communication_body_class(@post)
= render PhilomenaWeb.CommunicationView, "_body.html", object: @post, body: @body, conn: @conn, name: "post"
= render PhilomenaWeb.CommunicationView, "_body.html", object: @post, body: @body, conn: @conn, name: "post", options: options
.block__content.communication__options
.flex.flex--wrap.flex--spaced-out

View file

@ -1,29 +1,11 @@
div
' Posted
=> pretty_time(@post.created_at)
a.communication__interaction href=Routes.forum_topic_post_report_path(@conn, :new, @post.topic.forum, @post.topic, @post)
i.fa.fa-flag>
' Report
= if not is_nil(@post.edited_at) and can?(@conn, :show, @post) do
br
a href=Routes.forum_topic_post_history_path(@conn, :index, @post.topic.forum, @post.topic, @post)
' Edited
=> pretty_time(@post.edited_at)
= if @post.edit_reason not in [nil, ""] do
' because:
=> @post.edit_reason
div
- link_path = Routes.forum_topic_path(@conn, :show, @post.topic.forum, @post.topic, post_id: @post.id) <> "#post_#{@post.id}"
- safe_author = markdown_safe_author(@post)
- quote_body = if @post.hidden_from_users, do: "", else: @post.body
a.communication__interaction title="Link to post" href=link_path
i.fa.fa-link>
' Link
a.communication__interaction href=Routes.forum_topic_post_report_path(@conn, :new, @post.topic.forum, @post.topic, @post)
i.fa.fa-flag>
' Report
a.communication__interaction.post-reply.post-reply-quote href=link_path data-reply-url=link_path data-author=safe_author data-post=quote_body
i.fa.fa-quote-right>

View file

@ -36,25 +36,53 @@ defmodule PhilomenaWeb.ProfileView do
def commission_status(%{open: true}), do: "Open"
def commission_status(_commission), do: "Closed"
def sparkline_data(data) do
defp sparkline_y(val, max) do
# Filter out negative values
calc = max(val, 0)
# Lerp or 0 if not present
height = zero_div(calc * 20, max)
# In SVG coords, y grows down
20 - height
end
def sparkline_data(data, width \\ 375, height \\ 20) do
# Normalize range
max = max(Enum.max(data), 0)
sx = width / 90
sy = height / 20
factor = 100 / 90
content_tag :svg, width: "100%", preserveAspectRatio: "none", viewBox: "0 0 90 20" do
for {val, i} <- Enum.with_index(data) do
# Filter out negative values
calc = max(val, 0)
content_tag :svg, id: "js-sparkline-svg", width: "100%", preserveAspectRatio: "xMinYMin", viewBox: "0 0 #{width} #{height}" do
first = List.first(data)
last = List.last(data)
first_y = sparkline_y(first, max) * sy
last_y = sparkline_y(last, max) * sy
indexed_data = data
|> Enum.with_index()
points =
indexed_data
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [{cv, ci}, {nv, ni}] ->
cy = sparkline_y(cv, max)
ny = sparkline_y(nv, max)
# Lerp or 0 if not present
height = zero_div(calc * 20, max)
"C #{(ci * sx) + 0.5 * sx},#{cy * sy} #{(ni * sx) - 0.5 * sx},#{ny * sy} #{ni * sx},#{ny * sy}"
end)
|> Enum.join("")
# In SVG coords, y grows down
y = 20 - height
circles = for {val, i} <- indexed_data do
y = sparkline_y(val, max) * sy
content_tag :rect, class: "barline__bar", x: i, y: y, width: 1, height: height do
content_tag :circle, class: "barline__dot", cx: "#{i * factor}%", cy: y * sy + 1.25, r: 2.5 do
content_tag(:title, val)
end
end
graph = content_tag :path, "", id: "js-barline-graph", class: "barline__bar", d: "M0,#{first_y}#{points}L#{width - sx},#{last_y}L#{width - sx},#{height}L0,#{height}L0,#{first_y}"
[graph, circles]
end
end