Skip to content

Commit

Permalink
Merge pull request #73 from openzim/add_pinia
Browse files Browse the repository at this point in the history
Cleanup JS code and add header toolbar
  • Loading branch information
benoit74 authored Jan 21, 2025
2 parents f891b6a + fc833b6 commit 6286157
Show file tree
Hide file tree
Showing 23 changed files with 464 additions and 232 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Upgrade to Python 3.12 and Node.JS 22, adopt new openZIM practices, upgrade all dependencies (including zimscraperlib 5.0.0), add support for 'legacy' browsers (#43)
- Add clearer visual indication of external links (#70)
- Cleanup JS code with a Pinia store and add header toolbar (#73)

### Fixed

Expand Down
Empty file added scraper/tests/__init__.py
Empty file.
4 changes: 2 additions & 2 deletions zimui/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
autoprefixer: {}
}
}
16 changes: 15 additions & 1 deletion zimui/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
<script setup lang="ts">
import ErrorInfo from './components/ErrorInfo.vue'
import HeaderBar from './components/HeaderBar.vue'
import { useMainStore } from '@/stores/main'
const main = useMainStore()
</script>

<template>
<HeaderBar />
<Suspense>
<router-view></router-view>
<div v-if="main.errorMessage">
<ErrorInfo>
<p>{{ main.errorMessage }}</p>
<p>{{ main.errorDetails }}</p>
</ErrorInfo>
</div>
<router-view v-else></router-view>
</Suspense>
</template>

Expand Down
1 change: 1 addition & 0 deletions zimui/src/assets/freecodecamp-header.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion zimui/src/assets/vue.svg

This file was deleted.

68 changes: 68 additions & 0 deletions zimui/src/components/ErrorInfo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script setup lang="ts">
import dead_kiwix from '../assets/dead_kiwix.png'
</script>

<template>
<div class="page">
<div class="container">
<div class="content">
<p class="text"><slot></slot></p>
</div>
<div class="image-container">
<img :src="dead_kiwix" class="image" alt="error image" />
</div>
</div>
</div>
</template>

<style scoped>
.page {
max-width: 1200px;
margin: 0 auto;
padding: 40px;
text-align: center;
}
.container {
display: flex;
justify-content: space-between;
align-items: center;
gap: 40px;
margin: 60px 0;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
padding: 40px;
}
.content {
flex: 1;
text-align: left;
}
.text {
color: #222;
margin: 10px 0;
max-width: 500px;
line-height: 1.6;
font-size: 18px;
}
.image-container {
flex: 1;
display: flex;
justify-content: center;
}
@media (max-width: 768px) {
.container {
flex-direction: column;
text-align: center;
}
.text {
margin: 20px auto;
font-size: 16px;
}
.image {
width: 300px;
}
}
</style>
47 changes: 47 additions & 0 deletions zimui/src/components/HeaderBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useMainStore } from '@/stores/main'
import headerLogo from '@/assets/freecodecamp-header.svg'
// Fetch the home data
const main = useMainStore()
onMounted(async () => {
try {
await main.fetchLocales()
} catch {
main.setErrorMessage('An unexpected error occured.')
}
})
</script>

<template>
<div class="header-bar">
<div id="logo">
<router-link to="/">
<img id="logo" :src="headerLogo" alt="freeCodeCamp" class="logo" width="auto" height="70" />
</router-link>
</div>
</div>
</template>

<style scoped>
.header-bar {
background-color: #0a0a23;
}
#logo {
display: flex;
justify-content: center;
padding: 0.25rem;
}
a:after {
content: none;
}
@media print {
.header-bar {
display: none;
}
}
</style>
32 changes: 15 additions & 17 deletions zimui/src/components/challenge/ChallengeInstructions.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
<script setup lang="ts">
import { marked } from 'marked'
import { useMainStore } from '@/stores/main'
import { singlePathParam } from '@/utils/pathParams.ts'
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const props = defineProps<{
title: string
description: string
instructions: string
coursetitle: string
coursepath: string
curriculumtitle: string
}>()
const main = useMainStore()
const route = useRoute()
const superblock = computed(() => singlePathParam(route.params.superblock))
const course = computed(() => singlePathParam(route.params.course))
const render = (str: string): string => {
return marked.parse(str) as string
}
</script>

<template>
<!-- eslint-disable vue/no-v-html -->
<h1>{{ props.title }}</h1>
<h1>{{ main.challenge?.header['title'] }}</h1>
<p>
<RouterLink to="/">
&gt; {{ curriculumtitle }}
</RouterLink>
<RouterLink :to="coursepath">
&gt; {{ coursetitle }}
<RouterLink to="/"> &gt; {{ main.locales?.[superblock].title }} </RouterLink>
<RouterLink :to="`/${superblock}/${course}`">
&gt; {{ main.locales?.[superblock].blocks[course].title }}
</RouterLink>
</p>
<div class="markdown">
<div v-html="render(props.description)" />
<div v-html="render(main.challenge?.description || '')"></div>
<hr />
<div class="instructions" v-html="render(props.instructions)" />
<div class="instructions" v-html="render(main.challenge?.instructions || '')"></div>
</div>
</template>

Expand Down
39 changes: 21 additions & 18 deletions zimui/src/components/challenge/ChallengeRunner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import { ref, computed, watch, onMounted } from 'vue'
import { Challenge } from '@/utils/parseChallenge'
import type { RunResult } from '@/utils/runChallenge'
import { runChallenge } from '@/utils/runChallenge'
import type { ChallengeInfo } from '@/types/challenges'
import { useMainStore } from '@/stores/main'
import { singlePathParam } from '@/utils/pathParams.ts'
import { useRoute } from 'vue-router'
const props = defineProps<{
challenge: Challenge
solution: string
nextChallenge?: { title: string; url: string }
}>()
const main = useMainStore()
const route = useRoute()
const emit = defineEmits<{
(e: 'reset'): void
(e: 'setSolution'): void
(e: 'logs', logs: string[]): void
}>()
const superblock = computed(() => singlePathParam(route.params.superblock))
const course = computed(() => singlePathParam(route.params.course))
const slug = computed(() => singlePathParam(route.params.slug))
const nextChallenge: Ref<ChallengeInfo | undefined> = computed(() => {
return main.nextChallenge(slug.value)
})
const result: Ref<RunResult | null> = ref(null)
Expand All @@ -29,15 +32,15 @@ const passed: ComputedRef<boolean> = computed(() => {
})
const runTest = (): void => {
result.value = runChallenge(props.challenge, props.solution)
emit('logs', result.value.logs)
result.value = runChallenge(main.challenge as Challenge, main.solution)
main.logs = result.value.logs
}
result.value = runChallenge(props.challenge, '')
result.value = runChallenge(main.challenge as Challenge, '')
watch(
() => props.challenge,
() => main.challenge,
() => {
result.value = runChallenge(props.challenge, '')
result.value = runChallenge(main.challenge as Challenge, '')
}
)
Expand All @@ -47,17 +50,17 @@ onMounted(() => {
</script>

<template>
<button v-if="cheatMode" @click="emit('setSolution')">Set solution</button>
<button v-if="cheatMode" @click="main.cheatSolution()">Set solution</button>
<button @click="runTest">Run the tests</button>
<div v-if="passed" class="passed">
<p>Passed!</p>
<p v-if="nextChallenge">
<router-link :to="nextChallenge.url">
<router-link :to="`/${superblock}/${course}/${nextChallenge.slug}`">
<button>Move to next challenge</button>
</router-link>
</p>
</div>
<button @click="emit('reset')">Reset this lesson</button>
<button @click="main.resetSolution()">Reset this lesson</button>
<p v-for="(test, i) in result?.hints" :key="i" class="hint" :class="{ passed: test.passed }">
{{ test.description }}
</p>
Expand Down
15 changes: 5 additions & 10 deletions zimui/src/components/challenge/CodeEditor.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
<script setup lang="ts">
import { ref, shallowRef, watch } from 'vue'
import { shallowRef } from 'vue'
import { Codemirror } from 'vue-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { EditorView } from '@codemirror/view'
import { useMainStore } from '@/stores/main'
const props = defineProps<{ sourceCode: string }>()
const emit = defineEmits<{ (e: 'update', value: string): void }>()
const main = useMainStore()
const code = ref(props.sourceCode)
const extensions = [javascript()]
watch(props, () => {
code.value = props.sourceCode
})
// Codemirror EditorView instance ref
const view = shallowRef()
const handleReady = ({ view: editorView }: { view: EditorView }) => {
Expand All @@ -36,15 +31,15 @@ const handleReady = ({ view: editorView }: { view: EditorView }) => {
<template>
<div>
<codemirror
v-model="code"
v-model="main.solution"
placeholder="Code goes here..."
:style="{ height: '100%' }"
:autofocus="true"
:indent-with-tab="true"
:tab-size="2"
:extensions="extensions"
@ready="handleReady"
@change="emit('update', $event)"
@change="(event: any) => (main.solution = event)"
/>
</div>
</template>
Expand Down
7 changes: 4 additions & 3 deletions zimui/src/components/challenge/ConsoleLogger.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { toRef } from 'vue'
import { useMainStore } from '@/stores/main'
const main = useMainStore()
const props = defineProps<{
logs: string[]
syntaxError?: Error | null
}>()
const logs = toRef(props, 'logs')
const syntaxError = toRef(props, 'syntaxError')
</script>

Expand All @@ -16,7 +17,7 @@ const syntaxError = toRef(props, 'syntaxError')
{{ syntaxError.name }}
{{ syntaxError.message }}
</p>
<pre v-for="(log, i) in logs" v-else :key="i"> > {{ log }} </pre>
<pre v-for="(log, i) in main.logs" v-else :key="i"> > {{ log }} </pre>
</div>
</template>

Expand Down
3 changes: 3 additions & 0 deletions zimui/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// list of challenge types supported by current ZIM Vue.JS UI (other do not render
// properly because we do not yet implemented how to process/render the Markdown)
export const supportedChallengeTypes = ['1', '4', '5']
2 changes: 2 additions & 0 deletions zimui/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createRouter, createWebHashHistory } from 'vue-router'
import './style.css'
import App from './App.vue'
Expand All @@ -16,5 +17,6 @@ const router = createRouter({
})

app.use(router)
app.use(createPinia())

app.mount('#app')
Loading

0 comments on commit 6286157

Please sign in to comment.