ProfileActions.vala 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. using Inversion;
  2. using Spry;
  3. using Spry.Authentication;
  4. using Statum;
  5. using ClankerSaddle.Services;
  6. namespace ClankerSaddle {
  7. /**
  8. * The shipped Spry change-own-password action with this app's
  9. * post-change destination (the profile page) - the form fields and
  10. * verification (current/new/confirm) are Spry's.
  11. */
  12. public class ChangeMyPasswordAction : Spry.Actions.ChangePasswordAction {
  13. protected override string login_uri { get { return "/login"; } }
  14. }
  15. /**
  16. * The public shape of the DEVICE-scope `ui` slot (goal 41 task 2):
  17. * the theme + colour mode the client applies to
  18. * `document.documentElement` (`data-theme`/`data-mode`). Plain
  19. * strings on purpose — the slot is cached client-side across
  20. * sessions, so it must stay readable by old frames.
  21. */
  22. public class UiSlotPublic : Object {
  23. /** The Spry theme slug ("" = the core default). */
  24. public string theme { get; set; default = ""; }
  25. /** auto | light | dark. */
  26. public string mode { get; set; default = MODE_DEFAULT; }
  27. }
  28. /**
  29. * Saves the account's appearance choice (goal 41 task 2): the
  30. * form on /profile posts `theme` (the Spry core default "" or one
  31. * of the seven shipped theme slugs) and `mode`
  32. * (auto|light|dark). The choice is persisted per user in
  33. * `user_preferences` AND written to the DEVICE-scope `ui` slot —
  34. * the client's theme hook reads that slot on init and after every
  35. * statum render, so the new look applies immediately and survives
  36. * sessions on this device.
  37. */
  38. public class SetThemeAction : StatumAction {
  39. /** The seven optional themes shipped with Spry 0.3. */
  40. public const string[] THEME_SLUGS = {
  41. "airy", "dwarf", "formal", "glassy", "h2o", "living-sky", "modern"
  42. };
  43. /** The three colour modes. */
  44. public const string[] MODES = { "auto", "light", "dark" };
  45. /** The DEVICE-scope slot type the client theme hook reads. */
  46. public const string UI_SLOT_TYPE = "ui";
  47. private UserService users = inject<UserService>();
  48. private UserPreferenceService preferences = inject<UserPreferenceService>();
  49. /** True when `slug` names a theme the client may apply. */
  50. public static bool is_valid_theme(string slug) {
  51. if (slug == "" || slug == "core") {
  52. return true;
  53. }
  54. foreach (string candidate in THEME_SLUGS) {
  55. if (candidate == slug) {
  56. return true;
  57. }
  58. }
  59. return false;
  60. }
  61. /** True when `mode` is a colour mode the client may apply. */
  62. public static bool is_valid_mode(string mode) {
  63. foreach (string candidate in MODES) {
  64. if (candidate == mode) {
  65. return true;
  66. }
  67. }
  68. return false;
  69. }
  70. public override async DirectiveBuilder handle() throws GLib.Error {
  71. var guard = yield SpryAuth.require_login(directives(), request, users);
  72. if (guard != null) {
  73. return (!)guard;
  74. }
  75. string theme = Forms.field(request, "theme");
  76. string mode = Forms.field(request, "mode");
  77. if (mode.length == 0) {
  78. mode = MODE_DEFAULT;
  79. }
  80. if (!is_valid_theme(theme)) {
  81. return directives().notify("error",
  82. "Unknown theme '" + theme + "' - pick one of the themes offered below");
  83. }
  84. if (!is_valid_mode(mode)) {
  85. return directives().notify("error",
  86. "Unknown colour mode '" + mode + "' - use auto, light or dark");
  87. }
  88. var me = SpryAuth.current(request);
  89. try {
  90. yield preferences.set_theme(me.user_id, theme);
  91. yield preferences.set_mode(me.user_id, mode);
  92. var slot = new UiSlotPublic() { theme = theme, mode = mode };
  93. return directives()
  94. .set_typed<UiSlotPublic>(UI_SLOT_TYPE, Statum.Scope.DEVICE, slot)
  95. .notify("success", "Appearance saved — the new theme applies immediately");
  96. } catch (Error e) {
  97. return directives().notify("error",
  98. @"Could not save the appearance choice: $(e.message)");
  99. }
  100. }
  101. }
  102. }