aboutsummaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
authorFeuerfuchs <git@feuerfuchs.dev>2019-06-24 21:54:39 +0200
committerFeuerfuchs <git@feuerfuchs.dev>2019-06-24 21:54:39 +0200
commit3ec9264deb436af8fd6f5efc5efc3c7872127b05 (patch)
tree0906868494c3656ec3fba9f3824671a887e55b28 /js
parentShow expandable thumbnails for images (diff)
downloadgopherproxy-3ec9264deb436af8fd6f5efc5efc3c7872127b05.tar.gz
gopherproxy-3ec9264deb436af8fd6f5efc5efc3c7872127b05.tar.bz2
gopherproxy-3ec9264deb436af8fd6f5efc5efc3c7872127b05.zip
Improved design, add setting for image previews, add prompt for new location
Diffstat (limited to 'js')
-rw-r--r--js/KeyValueStore.ts89
-rw-r--r--js/main.ts191
2 files changed, 254 insertions, 26 deletions
diff --git a/js/KeyValueStore.ts b/js/KeyValueStore.ts
new file mode 100644
index 0000000..fd8f39e
--- /dev/null
+++ b/js/KeyValueStore.ts
@@ -0,0 +1,89 @@
1type KeyValueStoreCallback<T> = (value: T) => void;
2
3interface IKeyValueStoreParam<T> {
4 value: T;
5 valueRange?: T[];
6 callbacks?: KeyValueStoreCallback<T>[];
7}
8
9type KeyValueStoreData<T> = { [K in keyof T]: IKeyValueStoreParam<T[K]> };
10
11class KeyValueStore<T> {
12 constructor(private data: KeyValueStoreData<T>) {
13 for (const id of Object.keys(data)) {
14 const props = data[id as keyof typeof data];
15
16 if (props.valueRange && props.valueRange.indexOf(props.value) === -1) {
17 throw new Error(`Invalid value "${props.value}" for ID "${id}"`);
18 }
19 }
20 }
21
22 public getValue(id: keyof T) {
23 return this.data[id].value;
24 }
25
26 public setValue<K extends keyof T>(id: K, value: T[K]) {
27 const props = this.data[id];
28
29 if (props.valueRange) {
30 const valueIndex = props.valueRange.indexOf(value);
31 if (valueIndex === -1) {
32 throw new Error(`Invalid value "${value}" for ID "${id}"`);
33 }
34 }
35
36 props.value = value;
37
38 if (props.callbacks) {
39 props.callbacks.forEach(callback => {
40 callback(value);
41 });
42 }
43 }
44
45 public cycleValue<K extends keyof T>(id: K, dir: -1 | 1 = 1) {
46 const props = this.data[id];
47
48 if (!props) {
49 throw new Error(`Invalid ID "${id}"`);
50 }
51
52 let value = props.value;
53
54 if (props.valueRange) {
55 let valueIndex = props.valueRange.indexOf(value) + dir;
56
57 if (valueIndex >= props.valueRange.length) {
58 valueIndex = 0;
59 } else if (valueIndex < 0) {
60 valueIndex = props.valueRange.length - 1;
61 }
62
63 value = props.value = props.valueRange[valueIndex];
64 } else if (typeof value === "number") {
65 (value as number) += dir;
66 props.value = value;
67 } else {
68 throw new Error(`Can't cycle "${id}"`);
69 }
70
71 if (props.callbacks) {
72 props.callbacks.forEach(callback => {
73 callback(value);
74 });
75 }
76
77 return value;
78 }
79
80 public addCallback<K extends keyof T>(id: K, callback: KeyValueStoreCallback<T[K]>) {
81 const props = this.data[id];
82
83 if (!props.callbacks) {
84 props.callbacks = [];
85 }
86
87 props.callbacks.push(callback);
88 }
89}
diff --git a/js/main.ts b/js/main.ts
index 21e589d..7ef7020 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -1,42 +1,181 @@
1let linkQryEls = document.getElementsByClassName('link--QRY'); 1/// <reference path="./KeyValueStore.ts" />
2let i = linkQryEls.length; 2
3while (i--) { 3//
4 linkQryEls[i].addEventListener('click', e => { 4
5function ensureSetting(key: string, defaultValue: string): string {
6 let strValue = localStorage.getItem(key);
7 if (strValue === null) {
8 strValue = defaultValue;
9 localStorage.setItem(key, strValue);
10 }
11 return strValue;
12}
13
14const settings = new KeyValueStore({
15 imagePreviews: {
16 value: ensureSetting('image-previews', '1') === '1',
17 valueRange: [false, true]
18 }
19});
20
21//
22
23(() => {
24 const linkQryEls = document.getElementsByClassName('link--QRY');
25 let i = linkQryEls.length;
26 while (i--) {
27 linkQryEls[i].addEventListener('click', e => {
28 e.preventDefault();
29
30 const resp = prompt('Please enter required input: ', '');
31 if ((resp !== null) && (resp !== "")) {
32 window.location.href = (e.target as HTMLAnchorElement).href + '?' + resp;
33 }
34
35 return false;
36 });
37 }
38})();
39
40(() => {
41 const locationPrefixEl = document.getElementsByClassName('location__prefix')[0];
42 locationPrefixEl.addEventListener('click', e => {
5 e.preventDefault(); 43 e.preventDefault();
6 44
7 const resp = prompt('Please enter required input: ', ''); 45 const resp = prompt('Please enter new location: ', '');
8 if ((resp !== null) && (resp !== "")) { 46 if ((resp !== null) && (resp !== "")) {
9 window.location.href = (e.target as HTMLAnchorElement).href + '?' + resp; 47 window.location.href = window.location.origin + '/' + resp;
10 } 48 }
11 49
12 return false; 50 return false;
13 }); 51 });
14} 52})();
15 53
16let imgPreviewEls = document.getElementsByClassName('img-preview'); 54(() => {
17i = imgPreviewEls.length; 55 const settingImagePreviewEl = document.getElementsByClassName('setting--image-previews')[0];
18while (i--) { 56 const settingImagePreviewValueEl = settingImagePreviewEl.getElementsByClassName('setting__value')[0];
19 const imgPreviewEl = imgPreviewEls[i] as HTMLAnchorElement; 57 const settingImagePreviewCallback = (value: boolean, init = false) => {
20 const child = imgPreviewEl.children[0] as HTMLImageElement; 58 if (value) {
21 const thumbnailUrl = child.src; 59 generateImageThumbnails();
60 } else if (!init) {
61 removeImageThumbnails();
62 }
22 63
23 child.addEventListener('load', e => { 64 localStorage.setItem('image-previews', value ? '1' : '0');
24 child.classList.remove('faded'); 65 settingImagePreviewValueEl.textContent = value ? '[yes]' : '[no]';
25 }); 66 }
26 67
27 imgPreviewEls[i].addEventListener('click', e => { 68 settingImagePreviewValueEl.addEventListener('click', e => {
28 e.preventDefault(); 69 e.preventDefault();
70 settings.cycleValue('imagePreviews');
71 return false;
72 });
29 73
30 child.classList.add('faded'); 74 settingImagePreviewCallback(settings.getValue('imagePreviews'), true);
75 settings.addCallback('imagePreviews', settingImagePreviewCallback);
76})();
31 77
32 if (child.classList.contains('expanded')) { 78(() => {
33 child.classList.remove('expanded'); 79 const modalEls = document.getElementsByClassName('modal');
34 child.src = thumbnailUrl; 80 let i = modalEls.length;
35 } else { 81 while (i--) {
36 child.classList.add('expanded'); 82 const modalEl = modalEls[i];
37 child.src = imgPreviewEl.href; 83 const modalContentEl = modalEl.getElementsByClassName('modal__content')[0];
38 } 84 const modalCloseBtnEl = modalEl.getElementsByClassName('modal__close-btn')[0];
39 85
86 document.addEventListener('click', e => {
87 if (!modalEl.classList.contains('modal--visible')) {
88 return;
89 }
90 if (e.target !== modalContentEl && !modalContentEl.contains(e.target as Element)) {
91 modalEl.classList.remove('modal--visible');
92 e.preventDefault();
93 e.stopPropagation();
94 }
95 }, true);
96
97 document.addEventListener('keydown', e => {
98 if (!modalEl.classList.contains('modal--visible')) {
99 return;
100 }
101 if (e.keyCode === 27) {
102 modalEl.classList.remove('modal--visible');
103 }
104 });
105
106 modalCloseBtnEl.addEventListener('click', e => {
107 e.preventDefault();
108 modalEl.classList.remove('modal--visible');
109 return false;
110 });
111 }
112
113 //
114
115 const settingsBtnEl = document.getElementsByClassName('settings-btn')[0];
116 const settingsModalEl = document.getElementsByClassName('modal--settings')[0];
117
118 settingsBtnEl.addEventListener('click', e => {
119 e.preventDefault();
120 settingsModalEl.classList.add('modal--visible');
40 return false; 121 return false;
41 }); 122 });
123})();
124
125function generateImageThumbnails() {
126 const linkImgEls = document.querySelectorAll('.link--IMG, .link--GIF');
127 let i = linkImgEls.length;
128 while (i--) {
129 const linkImgEl = linkImgEls[i] as HTMLAnchorElement;
130 const thumbnailUrl = linkImgEl.href.replace(/^(.*?)\/I/, '$1/T');
131
132 const lineBreakEl = document.createTextNode('\n');
133
134 const typeAnnotEl = document.createElement('span');
135 typeAnnotEl.classList.add('type-annotation');
136 typeAnnotEl.textContent = ' -> ';
137
138 const thumbnailEl = document.createElement('img');
139 thumbnailEl.src = thumbnailUrl;
140 thumbnailEl.addEventListener('load', e => {
141 thumbnailEl.classList.remove('faded');
142 });
143
144 const thumbnailAnchorEl = document.createElement('a');
145 thumbnailAnchorEl.classList.add('img-preview');
146 thumbnailAnchorEl.href = linkImgEl.href;
147 thumbnailAnchorEl.addEventListener('click', e => {
148 e.preventDefault();
149
150 thumbnailEl.classList.add('faded');
151
152 if (thumbnailEl.classList.contains('expanded')) {
153 thumbnailEl.classList.remove('expanded');
154 thumbnailEl.src = thumbnailUrl;
155 } else {
156 thumbnailEl.classList.add('expanded');
157 thumbnailEl.src = thumbnailAnchorEl.href;
158 }
159
160 return false;
161 });
162
163 thumbnailAnchorEl.append(thumbnailEl);
164 linkImgEl.parentNode!.insertBefore(thumbnailAnchorEl, linkImgEl.nextSibling);
165 linkImgEl.parentNode!.insertBefore(typeAnnotEl, thumbnailAnchorEl);
166 linkImgEl.parentNode!.insertBefore(lineBreakEl, typeAnnotEl);
167 }
168}
169
170function removeImageThumbnails() {
171 const linkImgEls = document.querySelectorAll('.link--IMG, .link--GIF');
172 let i = linkImgEls.length;
173 while (i--) {
174 const linkImgEl = linkImgEls[i];
175 let j = 3;
176
177 while (j-- && linkImgEl.nextSibling) {
178 linkImgEl.nextSibling.remove();
179 }
180 }
42} 181}