Skip to content

Commit

Permalink
flair picker change proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
schlawg committed Oct 29, 2024
1 parent b523964 commit 0c5ae87
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 67 deletions.
5 changes: 1 addition & 4 deletions modules/feed/src/main/FeedUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,7 @@ final class FeedUi(helpers: Helpers, atomUi: AtomUi)(
)(form3.textarea(_)(rows := 10)),
form3.group(form("flair"), "Icon", half = false): field =>
form3
.flairPicker(field, Flair.from(form("flair").value), label = frag("Update icon"), anyFlair = true):
span(cls := "flair-container"):
Flair.from(form("flair").value).map(f => marker(f.some, "uflair".some))
,
.flairPicker(field, Flair.from(form("flair").value), anyFlair = true),
form3.action(form3.submit("Save"))
)

Expand Down
4 changes: 1 addition & 3 deletions modules/pref/src/main/ui/AccountPages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ final class AccountPages(helpers: Helpers, ui: AccountUi, flagApi: lila.core.use
): f =>
form3.textarea(f)(rows := 5)
),
form3.flairPickerGroup(form("flair"), u.flair, label = trans.site.setFlair())(
userSpan(u, withPowerTip = false, cssClass = "flair-container".some)
):
form3.flairPickerGroup(form("flair"), u.flair):
p(cls := "form-help"):
a(
href := s"${routes.Pref.form("display")}#showFlairs",
Expand Down
3 changes: 1 addition & 2 deletions modules/team/src/main/ui/FormUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ final class FormUi(helpers: Helpers, bits: TeamUi)(
private val explainInput = input(st.name := "explain", tpe := "hidden")

private def flairField(form: Form[?], team: Team)(using Context) =
form3.flairPickerGroup(form("flair"), Flair.from(form("flair").value), label = trans.site.setFlair()):
span(cls := "flair-container".some)(team.name, teamFlair(team.light))
form3.flairPickerGroup(form("flair"), Flair.from(form("flair").value))

private def textFields(form: Form[?])(using Context) = frag(
form3.group(
Expand Down
31 changes: 14 additions & 17 deletions modules/ui/src/main/helper/Form3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import lila.core.i18n.{ I18nKey as trans, Translate }
import lila.core.user.FlairApi
import lila.ui.ScalatagsTemplate.{ *, given }

final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi):

final class Form3(formHelper: FormHelper & I18nHelper & AssetHelper, flairApi: FlairApi):
import formHelper.{ transKey, given }

private val idPrefix = "form3"
Expand Down Expand Up @@ -246,29 +245,27 @@ final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi):
)

private lazy val exceptEmojis = data("except-emojis") := flairApi.adminFlairs.mkString(" ")
def flairPickerGroup(field: Field, current: Option[Flair], label: Frag)(view: Frag)(using Context): Tag =
def flairPickerGroup(field: Field, current: Option[Flair])(using Context): Tag =
group(field, trans.site.flair(), half = true): f =>
flairPicker(f, current, label)(view)
flairPicker(f, current)

def flairPicker(field: Field, current: Option[Flair], label: Frag, anyFlair: Boolean = false)(view: Frag)(
using ctx: Context
def flairPicker(field: Field, current: Option[Flair], anyFlair: Boolean = false)(using
ctx: Context
): Frag =
frag(
details(cls := "form-control emoji-details")(
summary(cls := "button button-metal button-no-upper")(
label,
":",
nbsp,
view
div(cls := "form-control emoji-details")(
div(cls := "emoji-popup-button")(
st.select(st.id := id(field), name := field.name, cls := "form-control")(
current.map(f => option(value := f, selected := ""))
),
img(src := current.fold("")(formHelper.flairSrc(_)))
),
hidden(field, current.map(_.value)),
div(
cls := "flair-picker",
cls := "flair-picker none",
(!ctx.me.exists(_.isAdmin) && !anyFlair).option(exceptEmojis)
)(
button(cls := "button button-metal emoji-remove")("clear")
)
),
current.isDefined.option(p:
button(cls := "button button-red button-thin button-empty text emoji-remove")(trans.site.delete())
)
)

Expand Down
2 changes: 1 addition & 1 deletion modules/ui/src/main/helper/FormHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import play.api.i18n.Lang
import lila.core.data.SimpleMemo

trait FormHelper:
self: I18nHelper =>
self: I18nHelper & AssetHelper =>

protected def flairApi: lila.core.user.FlairApi

Expand Down
31 changes: 15 additions & 16 deletions ui/analyse/src/study/studyForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,29 +123,28 @@ export function view(ctrl: StudyForm): VNode {
[
h('div.form-split.flair-and-name' + (ctrl.relay ? '.none' : ''), [
h('div.form-group', [
h('label.form-label', 'Flair'),
h('label.form-label', 'Flair'),
h(
'details.form-control.emoji-details',
'div.form-control.emoji-details',
{
hook: onInsert(el => flairPickerLoader(el)),
},
[
h('summary.button.button-metal.button-no-upper', [
h('span.flair-container', [
h('img.uflair', {
attrs: { src: data.flair ? site.asset.flairSrc(data.flair) : '' },
}),
]),
h('div.emoji-popup-button', [
h(
'select#study-flair.form-control',
{ attrs: { name: 'flair' } },
data.flair && h('option', { attrs: { value: data.flair, selected: true } }),
),
h('img', { attrs: { src: data.flair ? site.asset.flairSrc(data.flair) : '' } }),
]),
h('input#study-flair', {
attrs: { type: 'hidden', name: 'flair', value: data.flair || '' },
}),
h('div.flair-picker', {
attrs: { 'data-except-emojis': 'activity.lichess' },
}),
h(
'div.flair-picker.none',
{ attrs: { 'data-except-emojis': 'activity.lichess' } },
h(removeEmojiButton, 'clear'),
),
],
),
data.flair && h(removeEmojiButton, 'Delete'),
]),
h('div.form-group', [
h('label.form-label', { attrs: { for: 'study-name' } }, i18n.site.name),
Expand Down Expand Up @@ -288,4 +287,4 @@ export function view(ctrl: StudyForm): VNode {
});
}

const removeEmojiButton = emptyRedButton + '.text.emoji-remove';
const removeEmojiButton = 'button.button.button-metal.emoji-remove';
4 changes: 2 additions & 2 deletions ui/bits/src/bits.flairPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as emojis from 'emoji-mart';

type Config = {
element: HTMLElement;
close: () => void;
close: (e: PointerEvent) => void;
onEmojiSelect: (i?: { id: string; src: string }) => void;
};

Expand All @@ -28,7 +28,7 @@ export async function initModule(cfg: Config): Promise<void> {
};
const picker = new emojis.Picker(opts);

cfg.element.appendChild(picker as unknown as HTMLElement);
cfg.element.prepend(picker as unknown as HTMLElement);
cfg.element.classList.add('emoji-done');
$(cfg.element).find('em-emoji-picker').attr('trap-bypass', '1'); // disable mousetrap within the shadow DOM
}
Expand Down
75 changes: 54 additions & 21 deletions ui/bits/src/exports/flairPicker.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,61 @@
export default function flairPickerLoader(element: HTMLElement): void {
const parent = $(element).parent();
const close = () => element.removeAttribute('open');
import { frag } from 'common';

export default async function flairPickerLoader(element: HTMLElement): Promise<void> {
const selectEl = element.querySelector('select')!;
const pickerEl = element.querySelector('.flair-picker') as HTMLElement;
const removeEl = element.querySelector('.emoji-remove') as HTMLElement;
const isOpen = () => !pickerEl.classList.contains('none');

const toggle = () => {
if (isOpen() && (pickerEl.contains(document.activeElement) || document.activeElement === removeEl))
selectEl.focus();
pickerEl.classList.toggle('none');
};

const onEmojiSelect = (i?: { id: string; src: string }) => {
parent.find('input[name="flair"]').val(i?.id ?? '');
parent.find('.uflair').remove();
if (i) parent.find('.flair-container').append('<img class="uflair" src="' + i.src + '" />');
close();
element.querySelector('.emoji-popup-button option')?.remove();
if (i?.id) selectEl.append(frag('<option value="' + i.id + '" selected></option>'));
element.querySelector<HTMLImageElement>('.emoji-popup-button img')!.src = i?.src ?? '';
toggle();
};
parent.find('.emoji-remove').on('click', e => {

const onClick = async (e: Event) => {
if (e instanceof KeyboardEvent && e.key !== 'Enter' && e.key !== ' ') return;
e.preventDefault();
toggle();
selectEl.focus();
};

await Promise.all([
site.asset.loadCssPath('bits.flairPicker'),
site.asset.loadEsm('bits.flairPicker', {
init: {
element: element.querySelector('.flair-picker')!,
onEmojiSelect,
close: (e: PointerEvent) => {
if (!isOpen() || selectEl.contains(e.target as Node)) return;
toggle();
},
},
}),
]);

['mousedown', 'keydown'].forEach(t => selectEl.addEventListener(t, onClick));
removeEl.addEventListener('click', e => {
e.preventDefault();
onEmojiSelect();
$(e.target).remove();
});
$(element).on('toggle', () =>
Promise.all([
site.asset.loadCssPath('bits.flairPicker'),
site.asset.loadEsm('bits.flairPicker', {
init: {
element: element.querySelector('.flair-picker')!,
close,
onEmojiSelect,
},
}),
]),
);

element.closest('.dialog-content')?.addEventListener('click', (e: PointerEvent) => {
// em's onClickOutside callback does not trigger inside modal dialog, so do it manually
if (!isOpen() || [selectEl, pickerEl].some(el => el.contains(e.target as Node))) return;
e.preventDefault();
toggle();
});

if (!CSS.supports('selector(:has(option))')) {
// let old browsers set and remove flairs
element.querySelector('img')!.style.display = 'block';
element.querySelector<HTMLElement>('.emoji-remove')!.style.display = 'block';
}
}
4 changes: 4 additions & 0 deletions ui/common/css/component/_dialog.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
body:has(dialog.touch-scroll) {
overflow: hidden !important;
}

dialog {
@extend %box-radius, %popup-shadow;
position: fixed;
Expand Down
34 changes: 33 additions & 1 deletion ui/common/css/form/_emoji-picker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,41 @@
position: relative;
// ensure the emoji picker is above the text and its licon
z-index: 2;
margin-bottom: 1em;
width: 50px;

&:has(option) img,
&:has(option) .emoji-remove {
display: block;
}
}

.flair-picker {
position: absolute;
top: 106%;
}

.emoji-remove {
display: none;
position: absolute;
right: 1em;
bottom: 1em;
z-index: 3;
}

.emoji-popup-button {
position: relative;
width: 50px;

select.form-control {
position: absolute;
width: 50px;
}
img {
display: none;
position: absolute;
pointer-events: none;
inset: 10px;
width: 22px;
height: 22px;
}
}

0 comments on commit 0c5ae87

Please sign in to comment.