Skip to content

Commit

Permalink
Merge branch 'refactor'
Browse files Browse the repository at this point in the history
  • Loading branch information
letwebdev committed Oct 8, 2023
2 parents 5a5d235 + 47c0a70 commit 97c644d
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 375 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"fix-path-apk": "sed --in-place 's|/hackernews|.|g' 'android/app/src/main/assets/public/index.html'",
"sync": "npm run build && npm run sync-only",
"build-apk-only": "(cd android && bash ./gradlew assembleDebug)",
"sync-only": "cap sync && npm run fix-path-apk"
"sync-only": "cap sync && npm run fix-path-apk",
"push": "npm run build && git add -A && git commit && git push"
},
"dependencies": {
"@vueuse/core": "^10.4.1",
Expand Down
304 changes: 304 additions & 0 deletions src/components/ControlPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
<script setup lang="ts">
import { computed, ref } from "vue"
import { useSettingsStore } from "@/stores/settings"
import { useItemsStore } from "@/stores/items"
import { storeToRefs } from "pinia"
import { generateRandomInteger, shuffleArray } from "@/libs/math"
import type { Item, Lists, LiveData, LiveDataSet } from "@/libs/types"
const settings = useSettingsStore().settings
const items = storeToRefs(useItemsStore()).items
const emit = defineEmits(["showPrompt", "clearPrompt"])
const lists = ref<Lists>([
{ name: "topstories", description: "Top stories" },
{ name: "newstories", description: "New stories" },
{ name: "beststories", description: "Best stories" },
{ name: "askstories", description: "Ask stories" },
{ name: "showstories", description: "Show stories" },
{ name: "jobstories", description: "Job stories" },
// Current largest item id
{ name: "maxitem", description: "any" },
{ name: "updates", description: "Changed items" },
])
const liveDataSet: LiveDataSet = []
function fetchSelectedLists() {
const names: string[] = []
selected.value.forEach((list) => {
names.push(list.name)
})
fetchLists(names)
emit("showPrompt")
}
function fetchLists(listNames: string[]) {
listNames.forEach((listName: string) => {
fetchList(listName)
})
}
async function fetchList(listName: string = "topstories") {
/* console.log(listName) */
let liveDataToFetch
for (const element of liveDataSet) {
/* console.log(element) */
if (element.listName === listName) {
liveDataToFetch = element.liveData
break
}
}
/* console.log(liveDataToFetch) */
const itemIds = getItemIds(liveDataToFetch)
itemIds.forEach((itemId: number) => {
fetchItem(itemId)
})
}
function getItemIds(liveData: LiveData): number[] {
console.log("liveData:")
console.log(liveData)
let itemIds: number[]
if (typeof liveData === "number") {
console.log("Live data is currently largest item id: " + liveData)
itemIds = []
const maxItemId: number = liveData
for (let i = 0; i < settings.numberOfItemsFetchedEachTime.value; i++) {
const randomItemId = generateRandomInteger(maxItemId)
itemIds.push(randomItemId)
}
console.log("Fetch generated random ids: " + itemIds)
} else if (typeof liveData === "object") {
let itemIdsInLiveData: number[]
if (Array.isArray(liveData)) {
console.log("Live data is an array ")
itemIdsInLiveData = liveData
} else {
console.log("Live data is a non-array object ")
itemIdsInLiveData = liveData.items
}
if (settings.fetchingRandomly.value) {
itemIds = shuffleArray(itemIdsInLiveData)
} else {
itemIds = [...itemIdsInLiveData]
}
// Remove the ids of fetched items to prevent duplication
for (const element of liveDataSet) {
/* console.log(element) */
if (element.liveData === liveData) {
const start = settings.numberOfItemsFetchedEachTime.value
if (Array.isArray(element.liveData)) {
const end = element.liveData.length - 1
element.liveData = element.liveData.splice(start, end)
console.log(element.liveData)
} else {
const end = element.liveData.items.length - 1
element.liveData.items = element.liveData.items.splice(start, end)
console.log(element.liveData.items)
}
break
}
}
} else {
console.log("Unknwon live data type:")
console.log(liveData)
return [1]
}
return itemIds
}
const baseURL: URL = new URL("https://hacker-news.firebaseio.com/v0")
function fetchItem(id: number) {
itemsInQueue += 1
if (itemsInQueue > settings.numberOfItemsFetchedEachTime.value) {
return
}
const itemURL: URL = new URL(`${baseURL}/item/${id}.json?print=pretty`)
const discussURL: URL = new URL(`https://news.ycombinator.com/item?id=${id}`)
fetch(itemURL)
.then((response) => response.json())
.then((item: Item) => {
// Convert Unix time to readable time
const unixTime = new Date(item.time * 1000)
item.readableTime = `
${unixTime.getFullYear()}-${unixTime.getMonth() + 1}-${unixTime.getDate()}
${unixTime.getHours()}:${unixTime.getMinutes()}
`
item.discuss = discussURL
items.value.push(item)
})
.catch((error) => console.error(`Error fetching data: ${error.message}`))
}
async function fetchLiveData(listName: string = "topstories"): Promise<LiveData> {
try {
const listURL: URL = new URL(`${baseURL}/${listName}.json?print=pretty`)
const response = await fetch(listURL)
const liveData: LiveData = await response.json()
return liveData
} catch (error: any) {
console.log(`Error: ${error.message}`)
}
}
// Init
;(async () => {
// Prefetch live data
// TODO refresh live data when refresh()
emit("showPrompt")
for await (const list of lists.value) {
const liveData = await fetchLiveData(list.name)
const elementOfLiveDataSet = {
listName: list.name,
liveData: liveData,
}
liveDataSet.push(elementOfLiveDataSet)
if (list.name === "topstories") {
fetchList(list.name)
emit("clearPrompt")
}
}
fetchList("maxitem")
/* console.log(liveDataSet) */
//----
})()
let itemsInQueue: number = 0
function fetchMore() {
// Clear count
itemsInQueue = 0
fetchSelectedLists()
}
function refresh() {
clear()
fetchMore()
}
function clear() {
// Clear displayed items
items.value = []
}
const selected = ref([{ name: "topstories", description: "Top stories" }])
const descriptions = computed<string[]>(() => selected.value.map((option) => option.description))
function fetchingListsAfterSelection() {
settings.fetchingListsAfterSelection.value && fetchMore()
}
</script>
<template>
<section>
<h2>Control Panel</h2>
<button @click="refresh" class="refresh">Refresh</button>
<button @click="clear" class="clear">Clear</button>
<ul class="descriptionsOfSelected">
Selected:
<li v-for="description in descriptions" :key="description">{{ description }}</li>
</ul>
<select
v-model="selected"
multiple
v-on:change="fetchingListsAfterSelection"
class="selectedLists"
>
<option
v-for="list in lists"
:key="list.name"
:value="{ description: list.description, name: list.name }"
>
{{ list.description }}
</option>
</select>
<button @click="fetchMore" class="fetchMore">Fetch more</button>
</section>
</template>
<style scoped>
h2 {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
.controlPanel {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
:is(button) {
margin: 5px auto;
height: 4ex;
background-image: linear-gradient(135deg, #00f059 60%, #42f0a5);
border: none;
border-radius: 5px;
font-weight: bold;
color: white;
}
}
@media (min-width: 624px) {
/*
width>624px
*/
.controlPanel {
position: fixed;
margin: 0 0 10% 0;
width: 200px;
:is(select) {
margin: 5% 0;
max-width: 165px;
height: 230px;
}
:is(button) {
width: 120px;
margin: 5px auto;
height: 4ex;
background-image: linear-gradient(135deg, #00f059 60%, #42f0a5);
border: none;
border-radius: 5px;
font-weight: bold;
color: white;
cursor: pointer;
z-index: 2;
}
:is(button):active {
box-shadow: 2px 2px 5px #00ff00;
}
}
.settingItems,
article {
margin-left: 22%;
margin-bottom: 1%;
}
.selectedLists {
max-width: 35%;
float: left;
}
.descriptionsOfSelected {
display: none;
}
.refresh {
margin: 5% 0 0 11%;
}
}
@media (max-width: 624px) {
/* width <= 624 */
.controlPanel {
align-items: center;
justify-content: center;
width: 200px;
:is(button) {
width: 120px;
}
/* TODO long press the button to open control panel
*/
:is(button).fetchMore {
width: 50px;
height: 40px;
position: fixed;
right: 0%;
/* transform: scale(0.5); */
/* transform-origin: right; */
}
}
.refresh {
margin: 0%;
}
}
</style>
65 changes: 65 additions & 0 deletions src/components/ItemPost.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script setup lang="ts">
"use strict"
import { useSettingsStore } from "@/stores/settings"
import type { Item } from "@/libs/types"
const settings = useSettingsStore().settings
defineProps(["item"])
function setUrl(item: Item) {
return item.url ? item.url.toString() : item.discuss.toString()
}
function displayingLink(item: Item): boolean {
return (
settings.displayingItemLink.value &&
item.url !== undefined &&
settings.maximumLinkLengthToDisplay.value > item.url.length
)
}
</script>
<template>
<article>
<h2 v-show="settings.displayingItemTitle.value" class="itemTitle">
<a :href="setUrl(item)"> {{ item.title }}</a>
</h2>
<ul>
<p v-show="settings.displayingItemText.value" v-html="item.text"></p>
<li v-show="settings.displayingItemTime.value">time: {{ item.readableTime }}</li>
<li v-show="settings.displayingItemType.value">type: {{ item.type }}</li>
<li v-show="displayingLink(item)">
link: <a :href="item.url">{{ item.url }}</a>
</li>
<li v-show="settings.displayingItemId.value">id: {{ item.id }}</li>
<li v-show="settings.displayingItemDiscuss.value">
discuss: <a :href="item.discuss.toString()">{{ item.discuss }}</a>
</li>
</ul>
</article>
</template>
<style scoped>
h2 {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
.itemTitle:hover {
background-color: hsla(160, 100%, 37%, 0.2);
cursor: pointer;
}
* {
/* So that _ in long link on mobile phone wrapped */
word-break: break-all;
}
@media (max-width: 624px) {
/* width <= 624 */
h2 {
font-size: 120%;
}
}
.itemText {
position: relative;
margin-top: 10%;
}
article {
margin-left: 22%;
margin-bottom: 1%;
}
</style>
Loading

0 comments on commit 97c644d

Please sign in to comment.