-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the Game Gig event page #113
Changes from 21 commits
72320db
7a65e86
b7e742c
293b6bc
d2b7d1e
c7b6695
dca8442
74cd5ee
a82c407
c63a609
4774a7a
020ca52
d675e1f
f47dec3
6f72c79
45d5ce5
502bfc6
28c410b
9a621c9
129a222
7aa6208
41b5125
2b1e486
5b809a2
452d54c
2561af5
d27d9a6
0c1f6c0
323c7d1
4776fcc
4c6d4fd
45cdae2
435e0d6
616c7d9
6e2b1c5
77631b6
b05fe5f
d24ec90
42bd916
e248f42
6bedd71
2eee8f9
30c1ff3
bc9c90c
9ad9790
403798b
a2bd02d
aca7ecb
a7931aa
e4e3bfd
d4d75a4
3daa214
8eb2d00
68bd026
757c35c
100bc45
8e41e46
c86d65e
5ec4388
d31a02e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
import Kitura | ||
import HaCTML | ||
import LoggerAPI | ||
import HeliumLogger | ||
import DotEnv | ||
import SwiftyJSON | ||
|
||
struct HackathonController { | ||
static func handler(hackathon: Hackathon) -> RouterHandler { | ||
return { request, response, next in | ||
try response.send( | ||
hackathon.node.render() | ||
).end() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
function updateCountDownTimer() { | ||
const startDate = {{startDate}}; | ||
const endDate = {{endDate}}; | ||
const CountDownTimerId = {{id}}; | ||
const CountDownTimerPreId = {{preId}}; | ||
const beforeEventMessage = {{beforeEventMessage}} | ||
const duringEventMessage = {{duringEventMessage}} | ||
const afterEventMessage = {{afterEventMessage}} | ||
|
||
const current = new Date(); | ||
|
||
let timeLeft = 0; | ||
if(current < startDate) { | ||
document.getElementById(CountDownTimerPreId).innerHTML = beforeEventMessage; | ||
timeLeft = startDate.getTime() - current.getTime(); | ||
} else { | ||
document.getElementById(CountDownTimerPreId).innerHTML = duringEventMessage; | ||
timeLeft = endDate.getTime() - current.getTime(); | ||
} | ||
|
||
// Abort if time is up | ||
if(timeLeft < 0) | ||
{ | ||
document.getElementById(CountDownTimerId).innerHTML = afterEventMessage; | ||
return; | ||
} | ||
|
||
let hours = Math.floor(timeLeft / (1000*60*60)); | ||
let mins = Math.floor(timeLeft/(1000 * 60) - hours * 60); | ||
let secs = Math.floor(timeLeft/1000 - mins * 60 - hours * 60 * 60); | ||
|
||
document.getElementById(CountDownTimerId).innerHTML = | ||
hours + ":" + (mins<10?"0":"") + mins + "<span id=\"seconds\">:" + (secs<10?"0":"") + secs +"</span>"; | ||
} | ||
|
||
updateCountDownTimer(); | ||
|
||
setInterval(updateCountDownTimer,1000); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import HaCTML | ||
import Foundation | ||
|
||
struct CountDownTimer : Nodeable { | ||
let startDate : Date | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me this reads like the date that the timer starts counting down. If this is specific to events, maybe we could use some /// doc comments on this struct |
||
let endDate : Date | ||
let id = "CountDownTimer\(UUID().description)" | ||
|
||
let preId = "CountDownTimerPre\(UUID().description)" // the id of the countdown message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't love that we are changing the ID of an element. I expect we can get away without this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have created issue #170 for this, I think fixing this is low priority. |
||
let beforeEventMessage = "Time left to start" | ||
let duringEventMessage = "Time remaining" | ||
let afterEventMessage = "Time's up!" | ||
|
||
var node: Node { | ||
return Fragment( | ||
El.Div[ | ||
Attr.className => "CountDownTimer__pre", | ||
Attr.id => preId | ||
].containing(""), | ||
El.Div[ | ||
Attr.className => "CountDownTimer", | ||
Attr.id => id | ||
].containing("YOU SHOULD SEE THE TIME REMAINING HERE"), | ||
Script( | ||
file: "Hackathons/CountDownTimer.js", | ||
escapes: [ | ||
"startDate": startDate, "endDate": endDate, | ||
"id": id, "preId": preId, | ||
"beforeEventMessage": beforeEventMessage, | ||
"duringEventMessage": duringEventMessage, | ||
"afterEventMessage" : afterEventMessage | ||
] | ||
) | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
function updateClock() { | ||
const id = {{id}} | ||
const current = new Date(); | ||
document.getElementById(id).innerHTML = current.getHours()+":"+(current.getMinutes()<10?"0":"") + current.getMinutes(); | ||
} | ||
|
||
updateClock(); | ||
|
||
setInterval(updateClock,1000); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import HaCTML | ||
import Foundation | ||
|
||
struct CurrentTime : Nodeable { | ||
let id = "CurrentTime\(UUID().description)" | ||
|
||
var node: Node { | ||
return Fragment( | ||
El.Span[Attr.id => id, Attr.className => "CurrentTime"].containing("Current Time"), | ||
Script( | ||
file: "Hackathons/CurrentTime.js", | ||
escapes: ["id": id] | ||
) | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import HaCTML | ||
import Foundation | ||
|
||
// swiftlint:disable line_length | ||
|
||
|
||
extension String { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! We could move this out to somewhere else. Files for extensions conventionally take the form: e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created issue #171 for this. |
||
func idMangle() -> String { | ||
return self.replacingOccurrences(of: " ", with: "-").lowercased() | ||
} | ||
} | ||
|
||
// TODO: investigate if the information here can be extracted from the landing feature | ||
struct GameGig2017: Hackathon { | ||
let gameEngines = [ | ||
("Unreal Engine", "https://www.unrealengine.com"), | ||
("Unity", "https://unity3d.com/"), | ||
("LÖVE", "https://love2d.org/"), | ||
("GameMaker", "lhttps://www.yoyogames.com/gamemaker"), | ||
("raylib", "http://www.raylib.com/") | ||
] | ||
|
||
let socialMediaLinks = [ | ||
("Facebook Page", "https://www.facebook.com/hackersatcambridge"), | ||
("Facebook Event", "https://www.facebook.com/events/124219834921040/"), | ||
("Twitter", "https://twitter.com/hackersatcam") | ||
] | ||
|
||
let tutorials = [ | ||
("Web Dev with Mozilla", "https://globalgamejam.org/news/dev-web-mozilla") | ||
] | ||
|
||
/** | ||
* Creates a GameGigCard element, the title becomes its id (spaces are replaced with hyphons) | ||
*/ | ||
func GameGigCard(title: String, content: Nodeable) -> Node { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear from reading this what a GameGigCard is used for. For the whole game gig page? For sections? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Cards? Like, literally cards. I used the same naming conventions as for the home page here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just realised that reads more aggresively than I intended. What I'm saying is, how could I word it better? Because they best way to describe these is that they literally look like floating cards hovering over the background. |
||
return El.Div[Attr.className => "GameGigCard"].containing( | ||
El.Span[ | ||
Attr.className => "GameGigCard__title", | ||
Attr.id => title.idMangle() | ||
].containing(title), | ||
content | ||
) | ||
} | ||
|
||
func ListOfLinks(dict: [(String, String)]) -> Nodeable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yip |
||
return El.Ul.containing( | ||
dict.map { (key, value) in | ||
El.Li.containing( | ||
El.A[Attr.href => value].containing(key) | ||
) | ||
} | ||
) | ||
} | ||
|
||
func GameGigTwitterFeed() -> Nodeable { | ||
return El.Div[Attr.className => "GameGigTwitterFeed"].containing( | ||
TextNode( | ||
"<a class=\"twitter-timeline\" data-dnt=\"true\" href=\"https://twitter.com/hashtag/hacgamegig\" data-widget-id=\"927201930149093377\">#hacgamegig Tweets</a><script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+\"://platform.twitter.com/widgets.js\";fjs.parentNode.insertBefore(js,fjs);}}(document,\"script\",\"twitter-wjs\");</script>", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we have used multi-line strings to avoid the bunch of escapes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was added before then, and tbh the twitter feed isn't used anymore, so might as well delete this. |
||
escapeLevel: .unsafeRaw | ||
) | ||
) | ||
} | ||
|
||
// TODO: grab the rulse from the constitution Repo | ||
func Rules() -> Nodeable { | ||
return Fragment( | ||
El.P.containing("The aim of the Game Gig is to create a fun, exciting, original game from scratch in less than 12 hours. We've created a few simple rules to help the process go smoothly for everyone."), | ||
El.Ul.containing( | ||
El.Li.containing("Please respect and look after the Computer Lab. Note that no food or drinks are permitted in the Intel Laboratory, and we will be checking"), | ||
El.Li.containing("You can work on your game in a team of up to four people."), | ||
El.Li.containing("Game-making commences at 10:30 and finishes at 20:00."), | ||
El.Li.containing("You are free to do whatever you like with your game after the Game Gig. You own the copyright to all the material you create during the Game Gig."), | ||
El.Li.containing("You are free to use any tools or libraries available to you to create your game. You can start with any pre-existing code or content that you like and you are free to use third-party assets, as long as you let the judges know what you created yourself and what you didn't. Failure to do so could risk disqualification."), | ||
El.Li.containing("It's your responsibility to make sure that you have the right to use third-party assets (for example, that they are public domain or available under an appropriate license)."), | ||
El.Li.containing(TextNode( | ||
Text(markdown: "Be kind and considerate to your fellow hackers and our volunteers. We're all here to have fun! By participating in the hackathon, you agree to abide by this [Code of Conduct](https://hackcodeofconduct.org/).").html, | ||
escapeLevel: .unsafeRaw | ||
)) | ||
) | ||
) | ||
} | ||
|
||
func GameGigCardsContainer(content: Nodeable) -> Node { | ||
return El.Div[Attr.className => "GameGigCardsContainer"].containing( | ||
content | ||
) | ||
} | ||
|
||
func GameGigTopBanner() -> Node { | ||
return El.Div[Attr.className => "GameGigTopBanner"].containing( | ||
El.Div[Attr.className => "GameGigTopBanner__right"].containing(CurrentTime()), | ||
El.Div[Attr.className => "GameGigTopBanner__left"].containing("Hackers at Cambridge Game Gig 80's") | ||
) | ||
} | ||
|
||
func NavBar(elements: [(String, Nodeable)]) -> Node { | ||
return El.Div[Attr.className => "GameGigNavBar"].containing( | ||
El.Span[Attr.className => "GameGigNavBar__logo"].containing("Powered by Studio Gobo and Electric Square"), | ||
Fragment(elements.map{ title, content in | ||
El.Span[Attr.className => "GameGigNavBar__item"].containing( | ||
El.A[ | ||
Attr.href => "#\(title.idMangle())" | ||
].containing( | ||
title | ||
) | ||
) | ||
}) | ||
) | ||
} | ||
|
||
// Convert a given time in String format, (eg. "12:00"), to the corresponding | ||
// Swift Date object representing that time on the day of the event itself | ||
let eventDate = Date.from(year: 2017, month: 12, day: 1, hour: 0, minute: 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably clearer to move this into the function itself. It's a little confusing as it's named eventDate but is not the startDate of the game gig (since game gig doesn't start at midnight) so don't want it used elsewhere accidentally |
||
func gameGigDate(_ time: String) -> Date { | ||
// To the dirty work by converting the events date to a string, appending the time, and convertin to a date again | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙈 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could have been nicer just to do:
;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's why we do these code reviews! I spent way too long thinking about this :/ |
||
let outFormatter = DateFormatter() | ||
outFormatter.dateFormat = "yyyy-MM-dd" | ||
outFormatter.timeZone = TimeZone(identifier: "Europe/London") | ||
outFormatter.locale = Locale(identifier: "en_GB") | ||
|
||
let dateTimeString = outFormatter.string(from: eventDate) + " " + time | ||
|
||
let formatter = DateFormatter() | ||
formatter.dateFormat = "yyyy-MM-dd HH:mm" | ||
formatter.timeZone = TimeZone(identifier: "Europe/London") | ||
formatter.locale = Locale(identifier: "en_GB") | ||
let dateTime = formatter.date(from: dateTimeString) | ||
return dateTime ?? Date() | ||
} | ||
|
||
var node: Node { | ||
// Define all time related info | ||
let gigStartDate = gameGigDate("10:30") | ||
let gigEndDate = gameGigDate("20:30") | ||
|
||
|
||
let schedule = [ | ||
("Arrival", gameGigDate("10:00")), | ||
("Start Jamming!", gigStartDate), | ||
("Lunch", gameGigDate("12:00")), | ||
("Dinner", gameGigDate("18:00")), | ||
("Stop Jamming!", gigEndDate), | ||
("LT1 Prizes and wrap-up", gameGigDate("21:00")) | ||
] | ||
|
||
// Define the list of game gig "cards" that are displayed as content | ||
let gameGigCards = [ | ||
("Schedule", Schedule(schedule: schedule)), | ||
("Feed", GameGigTwitterFeed()), | ||
("Get Involved", ListOfLinks(dict: socialMediaLinks)), | ||
("Game Engines", ListOfLinks(dict: gameEngines)), | ||
("Tutorials", ListOfLinks(dict: tutorials)), | ||
("Rules", Rules()) | ||
] | ||
|
||
return UI.Pages.base( | ||
title: "Hackers at Cambridge Game Gig 80's", | ||
customStylesheets: ["gamegig2017"], | ||
content: Fragment( | ||
GameGigTopBanner(), | ||
NavBar(elements: gameGigCards), | ||
CountDownTimer(startDate: gigStartDate, endDate: gigEndDate), | ||
GameGigCardsContainer(content: Fragment( | ||
gameGigCards.map{ title, content in | ||
GameGigCard(title: title, content: content) | ||
} | ||
)) | ||
) | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import HaCTML | ||
|
||
public protocol Hackathon: Nodeable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because in the future we will have multiple hackathons. TBH I created this when I wasn't sure if there was going to be a set format or not. Shuold I remove? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would maybe be useful if there was any specific data that we needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think this will become useful once we have properly defined event streams, so I will leave it in. |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import HaCTML | ||
import Foundation | ||
|
||
// TODO: add the ability to highlight the item in the schedule best on the time! | ||
struct Schedule : Nodeable { | ||
let schedule: [(String, Date)] | ||
|
||
var node: Node { | ||
let formatter = DateFormatter() | ||
formatter.dateFormat = "HH:mm" | ||
formatter.timeZone = TimeZone(identifier: "Europe/London") | ||
formatter.locale = Locale(identifier: "en_GB") | ||
return El.Ul.containing( | ||
schedule.map {event, date in | ||
let timeString = formatter.string(from: date) | ||
return El.Li.containing( | ||
timeString, | ||
" ", | ||
event | ||
) | ||
} | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import HaCTML | ||
import DotEnv | ||
import Foundation | ||
|
||
protocol JavaScriptable { | ||
var javaScript: String {get} | ||
} | ||
|
||
extension String: JavaScriptable { | ||
var javaScript : String { | ||
// TODO: PROPERLY ESCAPE THIS! (eg. conver newlines to backslash) | ||
// NOTE: A JSON LIBRARY COULD BE USED FOR THIS! | ||
return "\"\(self)\"" | ||
} | ||
} | ||
|
||
extension Date: JavaScriptable { | ||
var javaScript : String { | ||
let calendar = Calendar.current | ||
let year = calendar.component(.year, from: self) | ||
// We have to subtract 1 from the month as JavaScript months count from 0 | ||
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date | ||
let month = calendar.component(.month, from: self) - 1 | ||
let day = calendar.component(.day, from: self) | ||
let hour = calendar.component(.hour, from: self) | ||
let minute = calendar.component(.minute, from: self) | ||
let second = calendar.component(.second, from: self) | ||
let milliseconds = 0 | ||
return "new Date(Date.UTC(\(year),\(month),\(day),\(hour),\(minute),\(second),\(milliseconds)))" | ||
} | ||
} | ||
|
||
struct UnsafeRawJavaScript { | ||
let rawScript : String | ||
var javaScript : String { | ||
return rawScript | ||
} | ||
} | ||
|
||
/** | ||
* This class is used in order load front-end scripts from a file relative to the current path for browsing pleasure. | ||
* | ||
* It depends on the build system loading files into the 'Data' folder before running. | ||
*/ | ||
struct Script : Nodeable { | ||
let file : String | ||
let escapes : [String: JavaScriptable] | ||
let directory : String = DotEnv.get("BACKEND_JS_DIR")! | ||
var node: Node { | ||
let pathToFile = directory + "/" + file | ||
do { | ||
var script = try String(contentsOfFile: pathToFile, encoding: .utf8) | ||
escapes.map({ key, value in | ||
// TODO: find out if there is a way of doing these escapes in a Type-Safe manner! | ||
script = script.replacingOccurrences(of: "{{\(key)}}", with: "\(value.javaScript)") | ||
}) | ||
return El.Script.containing(TextNode(script, escapeLevel: .unsafeRaw)) | ||
} catch { | ||
return El.Script.containing(TextNode("console.log(\"failed to load \(pathToFile)\");", escapeLevel: .unsafeRaw)) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's be consistent with semicolons
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting that we're going semicolonless in our gulpfile, following this standard
(up for discussion on this, can probably wait until later)