Skip to content

Commit

Permalink
[#107] Allow users to create custom themes for their stories
Browse files Browse the repository at this point in the history
  • Loading branch information
noahko96 committed Jul 14, 2022
1 parent e165d93 commit 88dc198
Show file tree
Hide file tree
Showing 27 changed files with 756 additions and 11 deletions.
8 changes: 7 additions & 1 deletion app/controllers/adventures_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ def set_adventure
@adventure = Adventure.find_by_slug!(params[:id])
end

def custom_theme
@adventure.custom_theme
end
helper_method :custom_theme

def adventure_params
params.require(:adventure).permit(
:title,
Expand All @@ -152,7 +157,8 @@ def adventure_params
:back_button,
:show_source,
:character_card,
:archived
:archived,
:custom_theme_id
)
end

Expand Down
105 changes: 105 additions & 0 deletions app/controllers/custom_themes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
class CustomThemesController < ApplicationController

def index
if !current_user
flash[:alert] = "You must be logged in to view your custom themes."
return redirect_to new_user_session_path
end

@custom_themes = current_user.custom_themes
end

def show
if custom_theme.user != current_user
flash[:alert] = "You can't view that custom theme."
return redirect_to root_url
end

@adventure = Adventure.find_by_slug!('lemonade-stand')
render layout: 'custom_theme_preview'
end

def new
if !current_user
flash[:alert] = "You must be logged in to create a custom theme."
return redirect_to new_user_session_path
end

@custom_theme = CustomTheme.new
end

def create
if !current_user
flash[:alert] = "You must be logged in to create a custom theme."
return redirect_to new_user_session_path
end

@custom_theme = CustomTheme.new(custom_theme_params)
custom_theme.user = current_user

if custom_theme.save
redirect_to custom_theme
else
display_errors(:create)
render :new
end
end

def edit
if custom_theme.user != current_user
flash[:alert] = "You can't modify that custom theme."
return redirect_to root_url
end
end

def update
if custom_theme.user != current_user
flash[:alert] = "You can't modify that custom theme."
return redirect_to root_url
end

if custom_theme.update(custom_theme_params)
redirect_to custom_theme_path(custom_theme)
else
display_errors(:update)
render :edit
end
end

private

def custom_theme
@custom_theme ||= CustomTheme.find_by_slug!(params[:id])
end
helper_method :custom_theme

def custom_theme_params
params.require(:custom_theme).permit(
:title,
:background_color,
:border_color,
:intro_image,
:scene_image,
:end_image,
:header_font_family,
:header_font_color,
:body_font_family,
:body_font_color,
:choice_font_family,
:choice_font_color,
:button_color,
:button_font_family,
:button_font_color
)
end

def display_errors(method)
# the slug validation technically "runs" on create, even though it is not a field
if method == :create
custom_theme.errors.delete(:slug)
end
if custom_theme.errors.any?
flash[:alert] = custom_theme.errors.full_messages.join(", ")
end
end
end
12 changes: 12 additions & 0 deletions app/javascript/packs/adventure-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const customThemeSection = document.getElementById("custom_theme_section");
const themeField = (document.getElementById("adventure_theme")) as HTMLSelectElement;

if (themeField && customThemeSection) {
themeField.addEventListener("change", function() {
if (themeField.value == "custom") {
customThemeSection.classList.remove("no-display");
} else {
customThemeSection.classList.add("no-display");
}
});
}
1 change: 1 addition & 0 deletions app/javascript/packs/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '../src/button.css'
import '../src/modal.css'
import '../src/css/default/AccountForm.css'
import '../src/css/default/AdventureList.css'
import '../src/css/default/CustomThemeForm.css'
import '../src/css/default/StoryIndex.css'
import '../src/css/default/ThemeDark.css'
import '../src/css/default/FormattingHelp.css'
Expand Down
28 changes: 28 additions & 0 deletions app/javascript/packs/custom-theme-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const headerFontField = (document.getElementById("custom_theme_header_font_family")) as HTMLSelectElement;
const bodyFontField = (document.getElementById("custom_theme_body_font_family")) as HTMLSelectElement;
const choiceFontField = (document.getElementById("custom_theme_choice_font_family")) as HTMLSelectElement;
const buttonFontField = (document.getElementById("custom_theme_button_font_family")) as HTMLSelectElement;

if (headerFontField) {
headerFontField.addEventListener("change", function() {
headerFontField.style.fontFamily = headerFontField.value;
});
}

if (bodyFontField) {
bodyFontField.addEventListener("change", function() {
bodyFontField.style.fontFamily = bodyFontField.value;
});
}

if (choiceFontField) {
choiceFontField.addEventListener("change", function() {
choiceFontField.style.fontFamily = choiceFontField.value;
});
}

if (buttonFontField) {
buttonFontField.addEventListener("change", function() {
buttonFontField.style.fontFamily = buttonFontField.value;
});
}
40 changes: 40 additions & 0 deletions app/javascript/packs/custom-theme-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Important: Order matters here. Otherwise our flow chart's layout
// calculations fail

import '../src/global.css'
import '../src/button.css'
import '../src/modal.css'

import * as React from 'react'
import * as ReactDOM from 'react-dom'

import Player from '../src/components/Player'
import { load } from '../src/persistance'

const slug = SEED.slug

async function render() {
const content = await load(slug)
const customThemePreview = document.getElementById('custom-theme-preview')

if (content) {
ReactDOM.render(
<Player
title={SEED.title}
description='This story about a lemonade stand is here to demo your custom theme.'
story={content.story}
meta={content.meta}
portMeta={content.portMeta}
theme='Custom'
isOffline={false}
backButton={true}
debuggable={true}
characterCard={true}
showSource={false}
/>,
customThemePreview
)
}
}

render()
26 changes: 19 additions & 7 deletions app/javascript/src/components/themes/DesertTheme.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,36 @@ body:after {
}

.DesertTheme .PlayerIntro {
background: url('../../images/desert-theme-intro-background.jpg') 0 0 no-repeat /
cover;
background-image: url('../../images/desert-theme-intro-background.jpg');
background-position: 0 0;
background-repeat: no-repeat;
background-size: cover;
margin-top: 0;
margin-bottom: 0;
}

.DesertTheme .PlayerScene {
background: url('../../images/desert-theme-scene-background.jpg') 0 0 no-repeat /
cover;
background-image: url('../../images/desert-theme-scene-background.jpg');
background-position: 0 0;
background-repeat: no-repeat;
background-size: cover;
margin-top: 0;
margin-bottom: 30px;
}

.DesertTheme .PlayerEnd {
background: url('../../images/desert-theme-end-background.jpg') 50% 50% no-repeat /
cover;
background-image: url('../../images/desert-theme-end-background.jpg');
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
margin-top: 0;
margin-bottom: 0;
text-align: center;
}

.DesertTheme .PlayerForeground {
max-width: 1184px;
margin: 0 auto;
margin: auto auto;
padding: 24px 36px;
width: 100%;
}
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/src/css/default/AccountForm.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@
display: block;
color: white;
}

.no-display {
display: none;
}
3 changes: 3 additions & 0 deletions app/javascript/src/css/default/CustomThemeForm.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.CustomThemeForm select {
font-size: 20px;
}
12 changes: 11 additions & 1 deletion app/models/adventure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ class Adventure < ApplicationRecord
"Light" => "light",
"Viget" => "viget",
"Space" => "space",
"Desert" => "desert"
"Desert" => "desert",
"Custom" => "custom",
}

FEATURE_GROUPS = [
Expand All @@ -17,13 +18,16 @@ class Adventure < ApplicationRecord
slug_from :slug_source

belongs_to :user, optional: true
belongs_to :custom_theme, optional: true

validates :title, :theme, presence: true

validates :password, length: (3..32), presence: true, if: :has_password

validates :age_limit, numericality: { only_integer: true }, presence: true, if: :has_age_limit

validate :custom_theme_set

def to_s
title
end
Expand Down Expand Up @@ -52,4 +56,10 @@ def editable_by?(current_user)
def slug_source
title
end

def custom_theme_set
if theme == 'custom' && custom_theme.nil?
errors.add(:custom_theme, "required when theme is set to 'Custom'")
end
end
end
73 changes: 73 additions & 0 deletions app/models/custom_theme.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
class CustomTheme < ApplicationRecord
include Sluggable

FONTS = [
["American Typewriter", "American Typewriter, serif"],
["Andale Mono", "Andale Mono, monospace"],
["Apple Chancery", "Apple Chancery, cursive"],
["Arial", "Arial, sans-serif"],
["Arial Narrow", "Arial Narrow, sans-serif"],
["Avantgarde", "Avantgarde, TeX Gyre Adventor, URW Gothic L, sans-serif"],
["Blippo", "Blippo, fantasy"],
["Bookman", "Bookman, URW Bookman L, serif"],
["Bradley Hand", "Bradley Hand, cursive"],
["Brush Script MT", "Brush Script MT, Brush Script Std, cursive"],
["Chalkduster", "Chalkduster, fantasy"],
["Comic Sans MS", "Comic Sans MS, Comic Sans, cursive"],
["Courier", "Courier, monospace"],
["Courier New", "Courier New, monospace"],
["Cursive", "cursive"],
["DejaVu Sans Mono", "DejaVu Sans Mono, monospace"],
["Didot", "Didot, serif"],
["Fantasy", "Fantasy"],
["FreeMono", "FreeMono, monospace"],
["Georgia", "Georgia, serif"],
["Gill Sans", "Gill Sans, sans-serif"],
["Helvetica", "Helvetica, sans-serif"],
["Impact", "Impact, fantasy"],
["Jazz LET", "Jazz LET, fantasy"],
["Luminari", "Luminari, fantasy"],
["Marker Felt", "Marker Felt, fantasy"],
["Monospace", "monospace"],
["New Century Schoolbook", "New Century Schoolbook, TeX Gyre Schola, serif"],
["Noto Sans", "Noto Sans, sans-serif"],
["OCR A Std", "OCR A Std, monospace"],
["Optima", "Optima, sans-serif"],
["Palatino", "Palatino, URW Palladio L, serif"],
["Sans-Serif", "sans-serif"],
["Serif", "serif"],
["Snell Roundhand", "Snell Roundhand, cursive"],
["Stencil Std", "Stencil Std, fantasy"],
["Times", "Times, Times New Roman, serif"],
["Trattatello", "Trattatello, fantasy"],
["Trebuchet MS", "Trebuchet MS, sans-serif"],
["URW Chancery L", "URW Chancery L, cursive"],
["Verdana", "Verdana, sans-serif"]
]

has_many :adventures

slug_from :slug_source

dragonfly_accessor :intro_image
dragonfly_accessor :scene_image
dragonfly_accessor :end_image

belongs_to :user

validates :title, presence: true

def to_s
title
end

def to_param
slug
end

private

def slug_source
title
end
end
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class User < ApplicationRecord
:recoverable, :rememberable, :trackable, :validatable

has_many :adventures, dependent: :destroy
has_many :custom_themes, dependent: :destroy

def is_admin?
email.in? [
Expand Down
Loading

0 comments on commit 88dc198

Please sign in to comment.