Skip to content

Commit 9a79f34

Browse files
committed
Made the navbar responsive. Resolves #8.
1 parent 6c71bb9 commit 9a79f34

9 files changed

Lines changed: 140 additions & 104 deletions

File tree

event.yml.example

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ organizers:
77
startTime: 2020-08-20T00:00:00+00:00
88
endTime: 2020-08-22T00:00:00+00:00
99
scoring: jeopardy
10-
flags: 1
1110
---
1211
- id: start
1312
name: Starter
@@ -82,26 +81,4 @@ flags: 1
8281
host: tcp.myrosettactfinstance.xyz
8382
port: 42000
8483
baseScore: 500
85-
- id: hidden
86-
name: Hidden
87-
hidden: true
88-
order: 3
89-
challenges:
90-
- id: supersecret
91-
title: Super Secret Challenge
92-
flag: 'myctf{I_can_see_everything}'
93-
difficulty: 6
94-
description: |-
95-
It is possible to set a challenge as hidden, which will require the users to activate them (currently via
96-
inputting the [Konami Code](https://en.wikipedia.org/wiki/Konami_Code) anywhere on the site). Hidden challenges
97-
are not visible unless explicitly activated.
98-
99-
Enabling of these events occurs by hitting the `/api/session/unhide` endpoint, with the user's authentication
100-
token, therefore anything that performs this action will enable them (including users hitting the endpoint
101-
manually, however it should be noted that the endpoint requires and validates anti-XSRF data).
102-
103-
The SPA can be customized to change the trigger.
104-
105-
baseScore: 500
106-
hidden: true
10784

src/RosettaCTF.UI/src/app/app.component.less

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
width: 1200px;
3535
margin: 0 auto;
3636

37-
@media (max-width: 1279px) {
37+
@media (max-width: 1240px) {
3838
width: 100%;
3939
margin: 0;
4040
padding: 0 12px;
@@ -55,7 +55,7 @@
5555
font-size: 20px;
5656
font-weight: 600;
5757

58-
@media (max-width: 1279px) {
58+
@media (max-width: 1240px) {
5959
padding: 0;
6060
}
6161
}

src/RosettaCTF.UI/src/app/challenges/challenges.component.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,26 +91,27 @@ export class ChallengesComponent implements OnInit, OnDestroy {
9191
}
9292

9393
openSolveDialog(challenge: IChallenge): void {
94-
this.eventDispatcher.emit("dialog",
95-
{
96-
componentType: SubmitFlagDialogComponent,
97-
defaults: {
98-
challenge,
99-
provideFlag: flag => this.submitFlag(flag)
100-
}
101-
});
94+
this.eventDispatcher.emit("dialog", {
95+
componentType: SubmitFlagDialogComponent,
96+
defaults: {
97+
challenge,
98+
provideFlag: (flag: IApiFlag) => this.submitFlag(flag)
99+
}
100+
});
102101
}
103102

104103
private async submitFlag(flag: IApiFlag): Promise<void> {
105-
waitClose(this.eventDispatcher);
104+
waitOpen(this.eventDispatcher);
106105
this.disableButtons = true;
107106

108107
const response = await this.api.submitSolve(flag);
109108
if (!response.isSuccess) {
110109
this.eventDispatcher.emit("error", { message: "Submitting flag failed.", reason: response.error });
110+
return;
111111
} else if (!response.result) {
112112
// tslint:disable-next-line: max-line-length
113113
this.eventDispatcher.emit("dialog", { componentType: ErrorDialogComponent, defaults: { message: "Your response was incorrect." } });
114+
return;
114115
}
115116

116117
const categories = await this.api.getCategories();

src/RosettaCTF.UI/src/app/common/buttons.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ button {
2727
font: 11pt 'IBM Plex Sans';
2828
font-weight: 450;
2929

30-
margin: 0 12px 0 0;
30+
margin: 2px 12px 2px 0;
3131
padding: 8px 24px;
3232

3333
box-shadow: 2px 2px 2px 0 #222;

src/RosettaCTF.UI/src/app/data/navbar.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/RosettaCTF.UI/src/app/labelled-button/labelled-button.component.less

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
display: inline-block;
1919
height: 100%;
2020
margin: 0; padding: 0;
21+
22+
@media (max-width: 859px) {
23+
display: block;
24+
width: 100%; height: auto;
25+
}
2126
}
2227

2328
.labelled-button-container {
@@ -69,6 +74,13 @@
6974
}
7075
}
7176

77+
@media (max-width: 859px) {
78+
display: block;
79+
width: 100%;
80+
margin: 0;
81+
position: initial;
82+
}
83+
7284
& > .label {
7385
font-weight: 450;
7486
white-space: nowrap;
@@ -83,6 +95,10 @@
8395
top: 64px;
8496
right: -8px;
8597

86-
background: rgba(34, 34, 34, 0.75);
98+
background: rgba(34, 34, 34, 0.75);
99+
100+
@media (max-width: 1240px) {
101+
display: none !important;
102+
}
87103
}
88104
}

src/RosettaCTF.UI/src/app/navbar/navbar.component.html

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@
1919
-->
2020

2121
<div class="navbar-root">
22-
<div class="navbar-title"><a [routerLink]="['/']">{{ configuration?.name || "RosettaCTF" }}</a></div>
23-
<div class="spacer"></div>
24-
<div class="buttons">
25-
<span #buttonContainer></span>
26-
<labelled-button [actionId]="'landing'" [actionDescription]="'Go to landing page'" [actionText]="'Home'" [actionRoute]="['/']"></labelled-button>
27-
<labelled-button [actionId]="'challenges'" [actionDescription]="'Show currently-available challenges'" [actionText]="'Challenges'" [actionRoute]="['/challenges']"></labelled-button>
28-
<labelled-button [actionId]="'scoreboard'" [actionDescription]="'Display current team ranking'" [actionText]="'Scoreboard'" [actionRoute]="['/scoreboard']"></labelled-button>
29-
<labelled-button [actionId]="'profile'" [actionDescription]="'View your user profile'" [actionText]="'Profile'" [actionRoute]="['/profile']"></labelled-button>
30-
<labelled-button [actionId]="'team'" [actionDescription]="'View your team\'s profile'" [actionText]="'Team'" [actionRoute]="['/team']"></labelled-button>
31-
<labelled-button *ngIf="!session.isAuthenticated else elseBlock" [actionId]="'login'" [actionDescription]="'Log in'" [actionText]="'Log in'" [actionRoute]="['/session/login']"></labelled-button>
22+
<div class="navbar-subroot">
23+
<div class="navbar-title"><a [routerLink]="['/']">{{ configuration?.name || "RosettaCTF" }}</a></div>
24+
<div class="spacer"></div>
25+
<div class="mobile-expander" (click)="toggleMenu()">Menu <span class="drop-arrow" [@rotateMenuArrow]="menuState">&#xe313;</span></div>
26+
</div>
27+
<div class="buttons" [@toggleMenuVisibility]="menuState" (@toggleMenuVisibility.done)="animated()">
28+
<labelled-button (click)="hideMenu()" [actionId]="'landing'" [actionDescription]="'Go to landing page'" [actionText]="'Home'" [actionRoute]="['/']"></labelled-button>
29+
<labelled-button (click)="hideMenu()" [actionId]="'challenges'" [actionDescription]="'Show currently-available challenges'" [actionText]="'Challenges'" [actionRoute]="['/challenges']"></labelled-button>
30+
<labelled-button (click)="hideMenu()" [actionId]="'scoreboard'" [actionDescription]="'Display current team ranking'" [actionText]="'Scoreboard'" [actionRoute]="['/scoreboard']"></labelled-button>
31+
<labelled-button (click)="hideMenu()" [actionId]="'profile'" [actionDescription]="'View your user profile'" [actionText]="'Profile'" [actionRoute]="['/profile']"></labelled-button>
32+
<labelled-button (click)="hideMenu()" [actionId]="'team'" [actionDescription]="'View your team\'s profile'" [actionText]="'Team'" [actionRoute]="['/team']"></labelled-button>
33+
<labelled-button (click)="hideMenu()" *ngIf="!session.isAuthenticated else elseBlock" [actionId]="'login'" [actionDescription]="'Log in'" [actionText]="'Log in'" [actionRoute]="['/session/login']"></labelled-button>
3234
<ng-template #elseBlock>
33-
<labelled-button [actionId]="'logout'" [actionDescription]="'Log out to have a nice walk outside. Just kidding, everyone knows you never leave your room.'" [actionText]="'Log out'" [actionRoute]="['/session/logout']"></labelled-button>
35+
<labelled-button (click)="hideMenu()" [actionId]="'logout'" [actionDescription]="'Log out to have a nice walk outside. Just kidding, everyone knows you never leave your room.'" [actionText]="'Log out'" [actionRoute]="['/session/logout']"></labelled-button>
3436
</ng-template>
3537
</div>
3638
</div>

src/RosettaCTF.UI/src/app/navbar/navbar.component.less

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,62 @@
2121
display: flex;
2222
flex-direction: row;
2323

24-
& > .navbar-title {
25-
flex: 0 0 auto;
26-
font-size: 36px;
27-
font-weight: 700;
28-
line-height: 64px;
29-
}
24+
overflow: hidden;
3025

31-
& > .spacer {
26+
& > .navbar-subroot {
27+
display: flex;
3228
flex: 100 0 auto;
29+
30+
& > .navbar-title {
31+
flex: 0 0 auto;
32+
font-size: 36px;
33+
font-weight: 700;
34+
line-height: 64px;
35+
}
36+
37+
& > .spacer {
38+
flex: 100 0 auto;
39+
}
40+
41+
& > .mobile-expander {
42+
display: none;
43+
44+
font-weight: 450;
45+
color: #eee;
46+
47+
& > .drop-arrow {
48+
display: inline-block;
49+
50+
font-family: 'Material Icons';
51+
font-weight: 400;
52+
font-size: 18px;
53+
line-height: 20px;
54+
55+
vertical-align: middle;
56+
}
57+
}
3358
}
3459

3560
& > .buttons {
3661
flex: 0 0 auto;
3762

3863
padding-left: 16px;
3964
}
65+
66+
@media (max-width: 859px) {
67+
display: block;
68+
69+
& > .navbar-subroot {
70+
& > .mobile-expander {
71+
display: block;
72+
}
73+
}
74+
75+
& > .buttons {
76+
display: block;
77+
padding-left: 0;
78+
}
79+
}
4080

4181
a {
4282
&:link, &:visited {

src/RosettaCTF.UI/src/app/navbar/navbar.component.ts

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414
// See the License for the specific language governing permissions and
1515
// limitations under the License.
1616

17-
import { Component, OnInit, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentRef, OnDestroy, Input } from "@angular/core";
18-
import { Router, ResolveEnd } from "@angular/router";
17+
import { Component, OnInit, ComponentRef, OnDestroy } from "@angular/core";
18+
import { animate, state, style, transition, trigger } from "@angular/animations";
1919

20-
import { INavbarData } from "../data/navbar";
2120
import { Observable, Subject } from "rxjs";
2221
import { takeUntil } from "rxjs/operators";
2322
import { IApiEventConfiguration } from "../data/api";
@@ -28,51 +27,51 @@ import { SessionProviderService } from "../services/session-provider.service";
2827
@Component({
2928
selector: "app-navbar",
3029
templateUrl: "./navbar.component.html",
31-
styleUrls: ["./navbar.component.less"]
30+
styleUrls: ["./navbar.component.less"],
31+
animations: [
32+
trigger("toggleMenuVisibility", [
33+
state("*", style({ height: "0" })),
34+
state("showing", style({ height: "0" })),
35+
state("hiding", style({ height: "0" })),
36+
state("visible", style({ height: "384px" })),
37+
transition("collapsed => showing", animate("0s")),
38+
transition("showing => visible", animate("0.1s")),
39+
transition("visible => hiding", animate("0.1s")),
40+
transition("hiding => collapsed", animate("0s"))
41+
]),
42+
trigger("rotateMenuArrow", [
43+
state("*", style({ transform: "rotate(0deg)" })),
44+
state("showing", style({ transform: "rotate(0deg)" })),
45+
state("hiding", style({ transform: "rotate(360deg)" })),
46+
state("visible", style({ transform: "rotate(180deg)" })),
47+
transition("collapsed => showing", animate("0s")),
48+
transition("showing => visible", animate("0.1s")),
49+
transition("visible => hiding", animate("0.1s")),
50+
transition("hiding => collapsed", animate("0s"))
51+
])
52+
]
3253
})
3354
export class NavbarComponent implements OnInit, OnDestroy {
3455

3556
private ngUnsubscribe = new Subject();
3657

37-
@ViewChild("buttonContainer", { read: ViewContainerRef, static: true })
38-
container: ViewContainerRef;
3958
private component: ComponentRef<any>;
4059

60+
menuState: "collapsed" | "showing" | "visible" | "hiding" = "collapsed";
61+
4162
configuration$: Observable<IApiEventConfiguration>;
4263
configuration: IApiEventConfiguration;
4364

4465
session$: Observable<ISession>;
4566
session: ISession;
4667

47-
constructor(private router: Router,
48-
private resolver: ComponentFactoryResolver,
49-
private configurationProvider: ConfigurationProviderService,
68+
constructor(private configurationProvider: ConfigurationProviderService,
5069
private sessionProvider: SessionProviderService) {
5170
this.configuration$ = this.configurationProvider.configurationChange;
5271
this.session$ = this.sessionProvider.sessionChange;
5372
}
5473

5574
ngOnInit(): void {
56-
this.router.events
57-
.pipe(takeUntil(this.ngUnsubscribe))
58-
.subscribe(x => {
59-
if (!(x instanceof ResolveEnd)) {
60-
return;
61-
}
62-
const data = x.state.root.children[0].data as INavbarData;
63-
64-
this.container.clear();
65-
if (!!this.component) {
66-
this.component.destroy();
67-
this.component = null;
68-
}
69-
70-
if (!!data.buttons) {
71-
const factory = this.resolver.resolveComponentFactory(data.buttons);
72-
this.component = this.container.createComponent(factory);
73-
}
74-
});
75-
7675
this.configuration$
7776
.pipe(takeUntil(this.ngUnsubscribe))
7877
.subscribe(x => { this.configuration = x; });
@@ -88,4 +87,26 @@ export class NavbarComponent implements OnInit, OnDestroy {
8887
this.ngUnsubscribe.next();
8988
this.ngUnsubscribe.complete();
9089
}
90+
91+
toggleMenu(): void {
92+
if (this.menuState === "collapsed") {
93+
this.menuState = "showing";
94+
} else if (this.menuState === "visible") {
95+
this.menuState = "hiding";
96+
}
97+
}
98+
99+
hideMenu(): void {
100+
this.menuState = "hiding";
101+
}
102+
103+
animated(): void {
104+
window.setTimeout(() => {
105+
if (this.menuState === "showing") {
106+
this.menuState = "visible";
107+
} else if (this.menuState === "hiding") {
108+
this.menuState = "collapsed";
109+
}
110+
}, 1);
111+
}
91112
}

0 commit comments

Comments
 (0)