summaryrefslogtreecommitdiff
path: root/src/clicks_display.ts
blob: d13d790f57511926778f36c9c5460a2e38b12746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import h337 from "heatmap.js"

namespace Clicks {
    export type ClickEvent = {
        x: number
        y: number
        tag: string
    }

    export type UrlEvent = {
        url: string
    }

    export type Event = ClickEvent | UrlEvent

    export interface Format {
        format: string
        version: string
        width: number
        height: number
        brands: string
        events: Event[]
    }
}

class PageData {
    readonly url: string
    readonly width: number
    readonly data: h337.HeatmapData<h337.DataPoint>

    constructor(url: string, width: number, data: h337.DataPoint[]) {
        this.url = url
        this.width = width
        let maxValue = 0
        for (const d of data) {
            if (d.value > maxValue) maxValue = d.value
        }
        this.data = {
            data: data,
            min: 0,
            max: maxValue,
        }
    }
}

class Display {
    private status: HTMLElement
    private frame_container: HTMLElement
    private frame: HTMLIFrameElement
    private url: HTMLSelectElement
    private heatmap: HTMLElement
    private data: PageData[] | undefined
    private map: h337.Heatmap<"value", "x", "y">
    private current: PageData | undefined

    constructor() {
        this.status = document.getElementById("status")!!
        this.frame_container = document.getElementById("frame_container")!!
        this.frame = document.getElementById("frame")!! as HTMLIFrameElement
        this.heatmap = document.getElementById("heatmap")!!
        this.url = document.getElementById("url")!! as HTMLSelectElement
        this.map = h337.create({
            container: this.heatmap,
        })

        this.url.addEventListener("change", (e) => {
            if (this.data === undefined) return
            this.show(this.data[this.url.selectedIndex])
        })
    }

    loadFile(file: File) {
        this.setStatus("Loading...")
        const reader = new FileReader()
        reader.addEventListener("load", () => {
            const data = JSON.parse(reader.result as string) as Clicks.Format
            if (data.format !== "clicks" || data.version !== "1.0") {
                this.setStatus("Unknown format")
                return
            }
            this.data = []
            const scaleX = data.width
            const scaleY = data.width
            let url: string | undefined
            let clicks: h337.DataPoint[] = []
            let map = new Map<string, number>()
            for (const event of data.events) {
                if ("url" in event) {
                    if (url !== undefined) {
                        console.log(clicks)
                        this.data.push(new PageData(url, data.width, clicks))
                    }
                    url = event.url
                    clicks = []
                    map.clear()
                } else {
                    const ce = event as Clicks.ClickEvent
                    const x = Math.round(ce.x * scaleX)
                    const y = Math.round(ce.y * scaleY)
                    const pos = `${x}x${y}`
                    const index = map.get(pos)
                    if (index === undefined) {
                        map.set(pos, clicks!!.length)
                        clicks.push({ x: x, y: y, value: 1 })
                    } else {
                        clicks[index].value++
                    }
                }
            }
            if (url !== undefined) {
                console.log(clicks)
                this.data.push(new PageData(url, data.width, clicks))
            }

            while (this.url.options.length > 0)
                this.url.options.remove(0)

            for (let i = 0; i < this.data.length; i++) {
                const option = document.createElement("option") as HTMLOptionElement
                option.value = `${i}`
                option.innerText = this.data[i].url
                this.url.options.add(option)
            }

            this.setStatus("Loaded")
            if (this.data.length > 0) {
                this.url.selectedIndex = 0
                this.show(this.data[0])
            }
        })
        reader.readAsText(file)
    }

    private setStatus(newStatus: string) {
        this.status.innerText = newStatus
    }

    private show(data: PageData) {
        if (this.current === data) return
        this.current = data
        this.frame_container.style.width = `${data.width}px`
        this.frame.src = data.url
        this.heatmap.style.transform = "translateY(0px)"
        this.frame.addEventListener("load", () => {
            this.frame.contentDocument?.addEventListener("scroll", () => {
                const y = this.frame.contentDocument!!.documentElement.scrollTop
                this.heatmap.style.transform = `translateY(${-y}px)`
            })
        })
        this.map.setData(data.data)
    }
}

document.addEventListener("DOMContentLoaded", () => {
    const file = document.getElementById("file")
    const display = new Display()
    file?.addEventListener("change", (event: Event) => {
        const files = (event.target as HTMLInputElement | null)!!.files
        if (files === null) return
        if (files.length > 0) {
            display.loadFile(files[0])
        }
    })
})