Skip to content

Commit 8bfbe1d

Browse files
committed
Move template icon download into client
My main goal is to make it easier to make the client not global, and the template downloader uses the globally configured client. We could change it to pass it in but I figure it is not unreasonable for the client to know how to fetch assets like icons in addition to fetching responses for API calls.
1 parent a098d3d commit 8bfbe1d

File tree

5 files changed

+163
-174
lines changed

5 files changed

+163
-174
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
package com.coder.gateway.icons
22

33
import com.intellij.openapi.util.IconLoader
4+
import com.intellij.ui.JreHiDpiUtil
5+
import com.intellij.ui.paint.PaintUtil
6+
import com.intellij.ui.scale.JBUIScale
7+
import java.awt.Component
8+
import java.awt.Graphics
9+
import java.awt.Graphics2D
10+
import java.awt.image.BufferedImage
11+
import javax.swing.Icon
412

513
object CoderIcons {
614
val LOGO = IconLoader.getIcon("coder_logo.svg", javaClass)
@@ -21,42 +29,123 @@ object CoderIcons {
2129

2230
val UNKNOWN = IconLoader.getIcon("unknown.svg", javaClass)
2331

24-
val ZERO = IconLoader.getIcon("0.svg", javaClass)
25-
val ONE = IconLoader.getIcon("1.svg", javaClass)
26-
val TWO = IconLoader.getIcon("2.svg", javaClass)
27-
val THREE = IconLoader.getIcon("3.svg", javaClass)
28-
val FOUR = IconLoader.getIcon("4.svg", javaClass)
29-
val FIVE = IconLoader.getIcon("5.svg", javaClass)
30-
val SIX = IconLoader.getIcon("6.svg", javaClass)
31-
val SEVEN = IconLoader.getIcon("7.svg", javaClass)
32-
val EIGHT = IconLoader.getIcon("8.svg", javaClass)
33-
val NINE = IconLoader.getIcon("9.svg", javaClass)
34-
35-
val A = IconLoader.getIcon("a.svg", javaClass)
36-
val B = IconLoader.getIcon("b.svg", javaClass)
37-
val C = IconLoader.getIcon("c.svg", javaClass)
38-
val D = IconLoader.getIcon("d.svg", javaClass)
39-
val E = IconLoader.getIcon("e.svg", javaClass)
40-
val F = IconLoader.getIcon("f.svg", javaClass)
41-
val G = IconLoader.getIcon("g.svg", javaClass)
42-
val H = IconLoader.getIcon("h.svg", javaClass)
43-
val I = IconLoader.getIcon("i.svg", javaClass)
44-
val J = IconLoader.getIcon("j.svg", javaClass)
45-
val K = IconLoader.getIcon("k.svg", javaClass)
46-
val L = IconLoader.getIcon("l.svg", javaClass)
47-
val M = IconLoader.getIcon("m.svg", javaClass)
48-
val N = IconLoader.getIcon("n.svg", javaClass)
49-
val O = IconLoader.getIcon("o.svg", javaClass)
50-
val P = IconLoader.getIcon("p.svg", javaClass)
51-
val Q = IconLoader.getIcon("q.svg", javaClass)
52-
val R = IconLoader.getIcon("r.svg", javaClass)
53-
val S = IconLoader.getIcon("s.svg", javaClass)
54-
val T = IconLoader.getIcon("t.svg", javaClass)
55-
val U = IconLoader.getIcon("u.svg", javaClass)
56-
val V = IconLoader.getIcon("v.svg", javaClass)
57-
val W = IconLoader.getIcon("w.svg", javaClass)
58-
val X = IconLoader.getIcon("x.svg", javaClass)
59-
val Y = IconLoader.getIcon("y.svg", javaClass)
60-
val Z = IconLoader.getIcon("z.svg", javaClass)
32+
private val ZERO = IconLoader.getIcon("0.svg", javaClass)
33+
private val ONE = IconLoader.getIcon("1.svg", javaClass)
34+
private val TWO = IconLoader.getIcon("2.svg", javaClass)
35+
private val THREE = IconLoader.getIcon("3.svg", javaClass)
36+
private val FOUR = IconLoader.getIcon("4.svg", javaClass)
37+
private val FIVE = IconLoader.getIcon("5.svg", javaClass)
38+
private val SIX = IconLoader.getIcon("6.svg", javaClass)
39+
private val SEVEN = IconLoader.getIcon("7.svg", javaClass)
40+
private val EIGHT = IconLoader.getIcon("8.svg", javaClass)
41+
private val NINE = IconLoader.getIcon("9.svg", javaClass)
6142

43+
private val A = IconLoader.getIcon("a.svg", javaClass)
44+
private val B = IconLoader.getIcon("b.svg", javaClass)
45+
private val C = IconLoader.getIcon("c.svg", javaClass)
46+
private val D = IconLoader.getIcon("d.svg", javaClass)
47+
private val E = IconLoader.getIcon("e.svg", javaClass)
48+
private val F = IconLoader.getIcon("f.svg", javaClass)
49+
private val G = IconLoader.getIcon("g.svg", javaClass)
50+
private val H = IconLoader.getIcon("h.svg", javaClass)
51+
private val I = IconLoader.getIcon("i.svg", javaClass)
52+
private val J = IconLoader.getIcon("j.svg", javaClass)
53+
private val K = IconLoader.getIcon("k.svg", javaClass)
54+
private val L = IconLoader.getIcon("l.svg", javaClass)
55+
private val M = IconLoader.getIcon("m.svg", javaClass)
56+
private val N = IconLoader.getIcon("n.svg", javaClass)
57+
private val O = IconLoader.getIcon("o.svg", javaClass)
58+
private val P = IconLoader.getIcon("p.svg", javaClass)
59+
private val Q = IconLoader.getIcon("q.svg", javaClass)
60+
private val R = IconLoader.getIcon("r.svg", javaClass)
61+
private val S = IconLoader.getIcon("s.svg", javaClass)
62+
private val T = IconLoader.getIcon("t.svg", javaClass)
63+
private val U = IconLoader.getIcon("u.svg", javaClass)
64+
private val V = IconLoader.getIcon("v.svg", javaClass)
65+
private val W = IconLoader.getIcon("w.svg", javaClass)
66+
private val X = IconLoader.getIcon("x.svg", javaClass)
67+
private val Y = IconLoader.getIcon("y.svg", javaClass)
68+
private val Z = IconLoader.getIcon("z.svg", javaClass)
69+
70+
fun fromChar(c: Char) = when (c) {
71+
'0' -> ZERO
72+
'1' -> ONE
73+
'2' -> TWO
74+
'3' -> THREE
75+
'4' -> FOUR
76+
'5' -> FIVE
77+
'6' -> SIX
78+
'7' -> SEVEN
79+
'8' -> EIGHT
80+
'9' -> NINE
81+
82+
'a' -> A
83+
'b' -> B
84+
'c' -> C
85+
'd' -> D
86+
'e' -> E
87+
'f' -> F
88+
'g' -> G
89+
'h' -> H
90+
'i' -> I
91+
'j' -> J
92+
'k' -> K
93+
'l' -> L
94+
'm' -> M
95+
'n' -> N
96+
'o' -> O
97+
'p' -> P
98+
'q' -> Q
99+
'r' -> R
100+
's' -> S
101+
't' -> T
102+
'u' -> U
103+
'v' -> V
104+
'w' -> W
105+
'x' -> X
106+
'y' -> Y
107+
'z' -> Z
108+
109+
else -> UNKNOWN
110+
}
111+
}
112+
113+
fun alignToInt(g: Graphics) {
114+
if (g !is Graphics2D) {
115+
return
116+
}
117+
118+
val rm = PaintUtil.RoundingMode.ROUND_FLOOR_BIAS
119+
PaintUtil.alignTxToInt(g, null, true, true, rm)
120+
PaintUtil.alignClipToInt(g, true, true, rm, rm)
121+
}
122+
123+
// We could replace this with com.intellij.ui.icons.toRetinaAwareIcon at
124+
// some point if we want to break support for Gateway < 232.
125+
fun toRetinaAwareIcon(image: BufferedImage): Icon {
126+
val sysScale = JBUIScale.sysScale()
127+
return object : Icon {
128+
override fun paintIcon(c: Component?, g: Graphics, x: Int, y: Int) {
129+
if (isJreHiDPI) {
130+
val newG = g.create(x, y, image.width, image.height) as Graphics2D
131+
alignToInt(newG)
132+
newG.scale(1.0 / sysScale, 1.0 / sysScale)
133+
newG.drawImage(image, 0, 0, null)
134+
newG.dispose()
135+
} else {
136+
g.drawImage(image, x, y, null)
137+
}
138+
}
139+
140+
override fun getIconWidth(): Int = if (isJreHiDPI) (image.width / sysScale).toInt() else image.width
141+
142+
override fun getIconHeight(): Int = if (isJreHiDPI) (image.height / sysScale).toInt() else image.height
143+
144+
private val isJreHiDPI: Boolean
145+
get() = JreHiDpiUtil.isJreHiDPI(sysScale)
146+
147+
override fun toString(): String {
148+
return "TemplateIconDownloader.toRetinaAwareIcon for $image"
149+
}
150+
}
62151
}

src/main/kotlin/com/coder/gateway/sdk/CoderRestClient.kt

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.coder.gateway.sdk
22

3+
import com.coder.gateway.icons.CoderIcons
4+
import com.coder.gateway.icons.toRetinaAwareIcon
35
import com.coder.gateway.models.WorkspaceAgentModel
46
import com.coder.gateway.sdk.convertors.InstantConverter
57
import com.coder.gateway.sdk.ex.AuthenticationResponseException
@@ -21,19 +23,25 @@ import com.coder.gateway.util.CoderHostnameVerifier
2123
import com.coder.gateway.util.coderSocketFactory
2224
import com.coder.gateway.util.coderTrustManagers
2325
import com.coder.gateway.util.getHeaders
26+
import com.coder.gateway.util.toURL
27+
import com.coder.gateway.util.withPath
2428
import com.google.gson.Gson
2529
import com.google.gson.GsonBuilder
2630
import com.intellij.openapi.util.SystemInfo
31+
import com.intellij.util.ImageLoader
32+
import com.intellij.util.ui.ImageUtil
2733
import okhttp3.Credentials
2834
import okhttp3.OkHttpClient
2935
import okhttp3.logging.HttpLoggingInterceptor
36+
import org.imgscalr.Scalr
3037
import retrofit2.Retrofit
3138
import retrofit2.converter.gson.GsonConverterFactory
3239
import java.net.HttpURLConnection
3340
import java.net.URL
3441
import java.time.Instant
3542
import java.util.*
3643
import javax.net.ssl.X509TrustManager
44+
import javax.swing.Icon
3745

3846
/**
3947
* In non-test code use DefaultCoderRestClient instead.
@@ -218,4 +226,31 @@ open class CoderRestClient(
218226

219227
return buildResponse.body()!!
220228
}
221-
}
229+
230+
231+
private val iconCache = mutableMapOf<Pair<String, String>, Icon>()
232+
233+
fun loadIcon(path: String, workspaceName: String): Icon {
234+
var iconURL: URL? = null
235+
if (path.startsWith("http")) {
236+
iconURL = path.toURL()
237+
} else if (!path.contains(":") && !path.contains("//")) {
238+
iconURL = url.withPath(path)
239+
}
240+
241+
if (iconURL != null) {
242+
val cachedIcon = iconCache[Pair(workspaceName, path)]
243+
if (cachedIcon != null) {
244+
return cachedIcon
245+
}
246+
val img = ImageLoader.loadFromUrl(iconURL)
247+
if (img != null) {
248+
val icon = toRetinaAwareIcon(Scalr.resize(ImageUtil.toBufferedImage(img), Scalr.Method.ULTRA_QUALITY, 32))
249+
iconCache[Pair(workspaceName, path)] = icon
250+
return icon
251+
}
252+
}
253+
254+
return CoderIcons.fromChar(workspaceName.lowercase().first())
255+
}
256+
}

src/main/kotlin/com/coder/gateway/sdk/TemplateIconDownloader.kt

-132
This file was deleted.

0 commit comments

Comments
 (0)