Skip to content

Commit 3962262

Browse files
authored
Presets (#1451)
1 parent 19e15cb commit 3962262

30 files changed

+703
-190
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.quarkus.code.model;
2+
3+
import java.util.List;
4+
5+
public record Preset(String key, String title, String icon, List<String> extensions) {
6+
}

base/src/main/java/io/quarkus/code/rest/CodeQuarkusResource.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.time.format.DateTimeFormatter;
3030
import java.util.ArrayList;
3131
import java.util.List;
32-
import java.util.function.Predicate;
3332
import java.util.logging.Logger;
3433
import java.util.stream.Collectors;
3534

@@ -39,6 +38,7 @@
3938
import jakarta.ws.rs.core.MediaType;
4039
import jakarta.ws.rs.core.Response;
4140

41+
import static io.quarkus.code.service.PlatformService.PRESETS;
4242
import static java.util.function.Predicate.not;
4343

4444
@Path("/")
@@ -146,6 +146,33 @@ public Uni<Response> extensionsForStream(
146146
return extensions(platformOnly, extensions, extensionId);
147147
}
148148

149+
@GET
150+
@Path("/presets")
151+
@Produces(MediaType.APPLICATION_JSON)
152+
@NoCache
153+
@Operation(operationId = "presets", summary = "Get the Quarkus Launcher list of Presets")
154+
@Tag(name = "Presets", description = "Preset related endpoints")
155+
@APIResponse(responseCode = "200", description = "List of Presets", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Preset.class, type = SchemaType.ARRAY)))
156+
public Uni<Response> presets() {
157+
String lastUpdated = platformService.cacheLastUpdated().format(FORMATTER);
158+
Response response = Response.ok(PRESETS)
159+
.header(LAST_MODIFIED_HEADER, lastUpdated)
160+
.build();
161+
return Uni.createFrom().item(response);
162+
}
163+
164+
@GET
165+
@Path("/extensions/presets/{streamKey}")
166+
@Produces(MediaType.APPLICATION_JSON)
167+
@NoCache
168+
@Operation(operationId = "presetsForStream", summary = "Get the Quarkus Launcher list of Presets")
169+
@Tag(name = "Presets", description = "Preset related endpoints")
170+
@APIResponse(responseCode = "200", description = "List of Presets for a certain stream", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Preset.class, type = SchemaType.ARRAY)))
171+
public Uni<Response> presetsForStream(
172+
@PathParam("streamKey") String streamKey) {
173+
return presets();
174+
}
175+
149176
private Uni<Response> extensions(
150177
boolean platformOnly,
151178
List<CodeQuarkusExtension> extensions,

base/src/main/java/io/quarkus/code/service/PlatformService.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.time.LocalDateTime;
1616
import java.time.ZoneOffset;
1717

18+
import io.quarkus.code.model.Preset;
1819
import io.quarkus.code.model.ProjectDefinition;
1920
import io.quarkus.code.model.Stream;
2021
import io.quarkus.devtools.commands.data.QuarkusCommandException;
@@ -47,6 +48,24 @@
4748
@Singleton
4849
public class PlatformService {
4950

51+
public static final List<Preset> PRESETS = List.of(
52+
new Preset("db-service", "Microservice with database", "/static/media/presets/db-service.svg",
53+
List.of("io.quarkus:quarkus-rest", "io.quarkus:quarkus-rest-jackson",
54+
"io.quarkus:quarkus-hibernate-orm-panache", "io.quarkus:quarkus-jdbc-postgresql")),
55+
new Preset("event-driven-kafka", "Event driven service with Kafka", "/static/media/presets/event-driven-kafka.svg",
56+
List.of("io.quarkus:quarkus-messaging-kafka")),
57+
new Preset("cli", "Command-line tool", "/static/media/presets/cli.svg",
58+
List.of("io.quarkus:quarkus-picocli")),
59+
new Preset("webapp-mvc", "Web app with Model-View-Controller", "/static/media/presets/webapp-mvc.svg",
60+
List.of("io.quarkiverse.renarde:quarkus-renarde", "io.quarkiverse.web-bundler:quarkus-web-bundler")),
61+
new Preset("webapp-npm", "Web app with NPM UI", "/static/media/presets/webapp-npm.svg",
62+
List.of("io.quarkus:quarkus-rest", "io.quarkiverse.quinoa:quarkus-quinoa")),
63+
new Preset("webapp-qute", "Web app with ServerSide Rendering", "static/media/presets/webapp-qute.svg",
64+
List.of("io.quarkiverse.qute.web:quarkus-qute-web", "io.quarkiverse.web-bundler:quarkus-web-bundler")),
65+
new Preset("ai-infused", "AI Infused service", "static/media/presets/ai-infused.svg",
66+
List.of("io.quarkiverse.langchain4j:quarkus-langchain4j-openai",
67+
"io.quarkiverse.langchain4j:quarkus-langchain4j-easy-rag")));
68+
5069
@Inject
5170
private PlatformConfig platformConfig;
5271

base/src/main/resources/web/community-app/theme.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@
103103
--extensionsPickerSearchBorderColorOnFocus: var(--secondary);
104104
--extensionsPickerSearchClearColor: var(--warningColor);
105105

106+
// Presets
107+
--presetsTitleTextColor: var(--extensionsPickerCategoryTextColor);
108+
--presetsCardBorderColor: #373668;
109+
--presetsCardTextColor: var(--textColor);
110+
106111
// Copy to clipboard
107112
--copyToClipboardBackgroundColor: #1f1f1f;
108113
--copyToClipboardBodyBackgroundColor: #1f1f1f;

base/src/main/resources/web/lib/components/api/code-quarkus-api.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,15 @@ export async function fetchPlatform(api: Api, streamKey?: string, platformOnly:
7777
if (platformCache.has(cacheKey)) {
7878
return platformCache.get(cacheKey);
7979
}
80-
const path = streamKey ? `/extensions/stream/${streamKey}?platformOnly=${String(platformOnly)}` : `/extensions?platformOnly=${String(platformOnly)}`;
80+
const extensionsPath = streamKey ? `/extensions/stream/${streamKey}?platformOnly=${String(platformOnly)}` : `/extensions?platformOnly=${String(platformOnly)}`;
81+
const presetsPath = streamKey ? `/presets/stream/${streamKey}` : `/presets`;
8182
const data = await Promise.all([
82-
fetch(`${api.backendUrl}${path}`, api.requestOptions)
83+
fetch(`${api.backendUrl}${extensionsPath}`, api.requestOptions)
8384
.catch(() => Promise.reject(new Error('Failed to fetch the Quarkus extensions list from the api'))),
8485
fetch(`${api.backendUrl}/streams`, api.requestOptions)
85-
.catch(() => Promise.reject(new Error('Failed to fetch the Quarkus stream list from the api')))
86+
.catch(() => Promise.reject(new Error('Failed to fetch the Quarkus stream list from the api'))),
87+
fetch(`${api.backendUrl}${presetsPath}`, api.requestOptions)
88+
.catch(() => Promise.reject(new Error('Failed to fetch the Quarkus extensions list from the api')))
8689
]);
8790
if (!data[0].ok) {
8891
throw new Error('Failed to load the Quarkus extension list');
@@ -95,6 +98,7 @@ export async function fetchPlatform(api: Api, streamKey?: string, platformOnly:
9598
let platform = {
9699
extensions: json[0],
97100
streams: json[1],
101+
presets: json[2],
98102
tagsDef: api.tagsDef || DEFAULT_TAGS
99103
};
100104
platformCache.set(cacheKey, platform);

base/src/main/resources/web/lib/components/api/model.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ export interface Extension {
4242
bom?: string;
4343
}
4444

45+
export interface Preset {
46+
key: string;
47+
title: string;
48+
icon: string;
49+
extensions: string[];
50+
resolvedExtensions?: Extension[];
51+
}
52+
4553
export interface PlatformMappedExtensions {
4654
mapped: Extension[];
4755
missing: string[];
@@ -50,6 +58,7 @@ export interface PlatformMappedExtensions {
5058
export interface Platform {
5159
extensions: Extension[];
5260
streams: Stream[];
61+
presets: Preset[];
5362
tagsDef: Tag[];
5463
}
5564

base/src/main/resources/web/lib/components/api/quarkus-project-utils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,13 @@ export function newDefaultProject(): QuarkusProject {
222222

223223
const FILTER_PARAM_NAME = 'extension-search';
224224

225+
export function isFilterEmpty(filterParam: string = '') {
226+
return filterParam.trim() === DEFAULT_FILTER.trim();
227+
}
228+
229+
225230
function syncParamsInQuery(api: Api, project: QuarkusProject | undefined, filterParam: string = ''): void {
226-
const filter = filterParam.trim() === DEFAULT_FILTER.trim() ? '' : filterParam;
231+
const filter = isFilterEmpty(filterParam) ? '' : filterParam;
227232
if (!project) {
228233
window.history.replaceState(null, '', `/?${formatParam(FILTER_PARAM_NAME, filter)}`);
229234
return;
@@ -264,7 +269,7 @@ export function getQueryParams(): object | undefined {
264269
return queryParams;
265270
}
266271

267-
export const DEFAULT_FILTER = 'origin:platform '
272+
export const DEFAULT_FILTER = ''
268273

269274
export function resolveInitialFilterQueryParam(queryParams = getQueryParams()): string {
270275
if (!queryParams || !queryParams[FILTER_PARAM_NAME]) {

base/src/main/resources/web/lib/components/code-quarkus.scss

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,17 +154,19 @@ code.code {
154154
color: var(--codeTextColor);
155155
}
156156

157-
button.btn.btn-base {
158-
border: 1px solid var(--buttonBorderColor);
157+
button.btn.btn-light {
158+
border: none;
159159
cursor: pointer;
160160
border-radius: 0;
161+
font-weight: bold;
161162
background-color: transparent;
162163
color: var(--linkTextColor);
163164

164165
svg {
165166
margin-top: 2px;
166167
margin-right: 5px;
167168
font-size: 0.8rem;
169+
color: var(--textColor);
168170
}
169171
}
170172

base/src/main/resources/web/lib/components/extensions-picker/extension-row.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ExtensionsOrigin } from './extensions-origin';
1010
export interface ExtensionRowProps extends ExtensionEntry {
1111
selected?: boolean;
1212
keyboardActived?: boolean;
13-
pickerLayout?: boolean;
13+
layout?: 'picker' | 'cart';
1414
buildTool?: string;
1515
tagsDef: TagEntry[];
1616

@@ -47,14 +47,15 @@ export function ExtensionRow(props: ExtensionRowProps) {
4747
const description = props.description || '...';
4848
const selected = props.selected || props.default;
4949
const ga = props.id.split(':');
50+
const id = ga[1] + (props.platform ? '' : `:${props.version}`);
5051
return (
5152
<div {...activationEvents} className={classNames('extension-row', {
5253
'keyboard-actived': props.keyboardActived,
5354
hover,
5455
selected,
5556
'by-default': props.default
5657
})} ref={ref} aria-label={props.id} >
57-
{props.pickerLayout && (
58+
{props.layout === 'picker' && (
5859
<div
5960
className="extension-selector"
6061
aria-label={`Switch ${props.id} extension`}
@@ -66,20 +67,20 @@ export function ExtensionRow(props: ExtensionRowProps) {
6667

6768
<div className="extension-summary">
6869
<span className="extension-name" title={`${props.name} (${props.version})`}>{props.name}</span>
69-
<span className="extension-id" title={props.id}> [{ga[1]}]</span>
70+
<span className="extension-id" title={props.id}> [{id}]</span>
7071
<ExtensionsOrigin platform={props.platform} />
7172
{props.tags && props.tags.map((s, i) => <ExtensionTags key={i} tagsDef={props.tagsDef} name={s} hover={hover}/>)}
7273
</div>
7374

74-
{!props.pickerLayout && (
75+
{props.layout === 'cart' && (
7576
<div
7677
className="extension-remove"
7778
>
7879
{hover && props.selected && <FaTrashAlt/>}
7980
</div>
8081
)}
8182

82-
{props.pickerLayout && (
83+
{props.layout === 'picker' && (
8384
<React.Fragment>
8485
<div
8586
className="extension-description" title={description}

base/src/main/resources/web/lib/components/extensions-picker/extension-search-bar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export interface ExtensionSearchBarProps {
1818
result: FilterResult
1919
}
2020

21-
function ListItem(props: { className?: string, children: React.ReactChildren }) {
21+
function ListItem(props: { className?: string, children: React.ReactNode }) {
2222
const className = `${props.className || ''} list-item`;
2323
return (
2424
<div {...props} className={className}>{props.children}</div>
@@ -60,7 +60,7 @@ function FilterShortcutsDropdown(props: ExtensionSearchBarProps) {
6060
return (
6161
<Dropdown className="filter-shortcut" onClick={(e) => e.stopPropagation()} onToggle={setIsOpen} show={isOpen}>
6262
<Dropdown.Toggle className="filter-shortcut-button" aria-label="Toggle search filters">
63-
Filters {isOpen ? <FaAngleUp /> : <FaAngleDown />}
63+
Search {isOpen ? <FaAngleUp /> : <FaAngleDown />}
6464
</Dropdown.Toggle>
6565
<Dropdown.Menu
6666
align="left"

0 commit comments

Comments
 (0)