build concerns, build matrix
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
class Api::BuildsController < ApiController
|
||||
def index
|
||||
render json: BuildsService.new.index
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class Api::SoftwareBuildsController < ApiController
|
||||
def show
|
||||
render json: BuildsService.new.show(params[:name])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
class BuildsService
|
||||
def index
|
||||
platforms = UpdateService::SUPPORTED_PLATFORMS.each_with_object({}) do |platform, hash|
|
||||
service_class = "SoftwareUpdater::#{platform.camelize}Service".constantize
|
||||
hash[platform] = {
|
||||
label: service_class.label,
|
||||
kinds: service_class.expected_kinds
|
||||
}
|
||||
end
|
||||
|
||||
all_kinds = ReleaseAsset::KINDS
|
||||
|
||||
{ platforms: platforms, allKinds: all_kinds }
|
||||
end
|
||||
|
||||
def show(name)
|
||||
software = Software.find_by!(name: name)
|
||||
service_class = "SoftwareUpdater::#{software.platform.camelize}Service".constantize
|
||||
expected = service_class.expected_kinds
|
||||
|
||||
releases = software.releases.includes(:release_assets).order(updated_at: :desc)
|
||||
|
||||
release_data = releases.each_with_object({}) do |release, hash|
|
||||
actual = release.release_assets.map(&:kind)
|
||||
hash[release.version] = {
|
||||
actual: actual,
|
||||
missing: expected - actual
|
||||
}
|
||||
end
|
||||
|
||||
{
|
||||
platform: software.platform,
|
||||
expected: expected,
|
||||
releases: release_data
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
module SoftwareUpdater
|
||||
module BinaryAttachment
|
||||
# fájlnév slug → ReleaseAsset kind
|
||||
TARGET_KINDS = {
|
||||
"win-x86" => "win_x86",
|
||||
"win-x64" => "win_x64",
|
||||
"linux-x86" => "linux_x86",
|
||||
"linux-x64" => "linux_x64",
|
||||
"mac-x64" => "mac_x64",
|
||||
"mac-arm64" => "mac_arm64",
|
||||
"mac-universal" => "mac_universal"
|
||||
}.freeze
|
||||
|
||||
private
|
||||
|
||||
def binary_asset_paths(versioned)
|
||||
TARGET_KINDS.each_with_object({}) do |(slug, kind), assets|
|
||||
filename = "#{versioned}-#{slug}.zip"
|
||||
assets[kind] = full_path(filename) if File.file?(full_path(filename))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildCartridge
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "cartridge"
|
||||
end
|
||||
|
||||
def cartridge_ext
|
||||
raise NotImplementedError, "#{self.class} must implement #cartridge_ext"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cartridge_asset_path(versioned)
|
||||
path = full_path("#{versioned}#{cartridge_ext}")
|
||||
{ "cartridge" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildDocs
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "docs"
|
||||
register_prepare_step :prepare_docs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_docs(versioned)
|
||||
docs_zip = "#{versioned}-docs.zip"
|
||||
extract_zip_to_dir(docs_zip, "#{versioned}-docs") if File.file?(full_path(docs_zip))
|
||||
end
|
||||
|
||||
def docs_asset_path(versioned)
|
||||
docs_dir = "#{versioned}-docs"
|
||||
{ "docs" => full_path(docs_dir) } if dir_exists?(docs_dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildLinuxX64
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "linux_x64"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def linux_x64_asset_path(versioned)
|
||||
path = full_path("#{versioned}-linux-x64.zip")
|
||||
{ "linux_x64" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildMacArm64
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "mac_arm64"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mac_arm64_asset_path(versioned)
|
||||
path = full_path("#{versioned}-mac-arm64.zip")
|
||||
{ "mac_arm64" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildMacUniversal
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "mac_universal"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mac_universal_asset_path(versioned)
|
||||
path = full_path("#{versioned}-mac-universal.zip")
|
||||
{ "mac_universal" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildMacX64
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "mac_x64"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mac_x64_asset_path(versioned)
|
||||
path = full_path("#{versioned}-mac-x64.zip")
|
||||
{ "mac_x64" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildSource
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "source"
|
||||
end
|
||||
|
||||
def source_ext
|
||||
raise NotImplementedError, "#{self.class} must implement #source_ext"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def source_asset_path(versioned)
|
||||
path = full_path("#{versioned}#{source_ext}")
|
||||
{ "source" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildWeb
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "html"
|
||||
register_prepare_step :prepare_web
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_web(versioned)
|
||||
extract_zip_to_dir("#{versioned}.html.zip", versioned)
|
||||
end
|
||||
|
||||
def html_asset_path(versioned)
|
||||
path = full_path(versioned)
|
||||
{ "html" => path } if dir_exists?(versioned)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildWinX64
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "win_x64"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def win_x64_asset_path(versioned)
|
||||
path = full_path("#{versioned}-win-x64.zip")
|
||||
{ "win_x64" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
module SoftwareUpdater
|
||||
module Builds
|
||||
module BuildWinX86
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
register_expected_kind "win_x86"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def win_x86_asset_path(versioned)
|
||||
path = full_path("#{versioned}-win-x86.zip")
|
||||
{ "win_x86" => path } if File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,13 +4,33 @@ module SoftwareUpdater
|
||||
include ArchiveExtraction
|
||||
include MetadataParsing
|
||||
include SoftwarePersistence
|
||||
include BinaryAttachment
|
||||
|
||||
class_methods do
|
||||
def platform(value = nil)
|
||||
@platform = value if value
|
||||
@platform ||= name.demodulize.delete_suffix("Service").downcase
|
||||
end
|
||||
|
||||
def label(value = nil)
|
||||
@label = value if value
|
||||
@label ||= platform.titleize
|
||||
end
|
||||
|
||||
def expected_kinds
|
||||
@expected_kinds ||= []
|
||||
end
|
||||
|
||||
def register_expected_kind(kind)
|
||||
expected_kinds << kind unless expected_kinds.include?(kind)
|
||||
end
|
||||
|
||||
def prepare_steps
|
||||
@prepare_steps ||= []
|
||||
end
|
||||
|
||||
def register_prepare_step(method_name)
|
||||
prepare_steps << method_name unless prepare_steps.include?(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
def update(name, version)
|
||||
@@ -27,7 +47,7 @@ module SoftwareUpdater
|
||||
upsert_external_link(software.id, "Repository", repo_url) if repo_url.present?
|
||||
|
||||
release = create_release_if_not_exists(software_id: software.id, version: version)
|
||||
sync_release_assets(release, asset_paths(versioned).merge(binary_asset_paths(versioned)))
|
||||
sync_release_assets(release, collect_asset_paths(versioned))
|
||||
release
|
||||
end
|
||||
end
|
||||
@@ -35,15 +55,18 @@ module SoftwareUpdater
|
||||
private
|
||||
|
||||
def prepare_files(versioned)
|
||||
extract_zip_to_dir("#{versioned}.html.zip", versioned)
|
||||
self.class.prepare_steps.each { |step| send(step, versioned) }
|
||||
end
|
||||
|
||||
def parse_metadata(versioned)
|
||||
parse_json_metadata(full_path("#{versioned}.metadata.json"))
|
||||
end
|
||||
|
||||
def asset_paths(versioned)
|
||||
{ "html" => full_path(versioned) }
|
||||
def collect_asset_paths(versioned)
|
||||
self.class.expected_kinds.each_with_object({}) do |kind, hash|
|
||||
result = send(:"#{kind}_asset_path", versioned)
|
||||
hash.merge!(result) if result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
module SoftwareUpdater
|
||||
class BevyService
|
||||
include Updatable
|
||||
include Builds::BuildWeb
|
||||
include Builds::BuildWinX64
|
||||
include Builds::BuildLinuxX64
|
||||
|
||||
label "Bevy"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
module SoftwareUpdater
|
||||
class C64Service
|
||||
include Updatable
|
||||
include Builds::BuildCartridge
|
||||
|
||||
private
|
||||
label "C64"
|
||||
|
||||
def prepare_files(_versioned)
|
||||
# a c64 buildhez nem tartozik html zip, csak a .prg fájl
|
||||
end
|
||||
|
||||
def asset_paths(versioned)
|
||||
{ "cartridge" => full_path("#{versioned}.prg") }
|
||||
end
|
||||
def cartridge_ext = ".prg"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
module SoftwareUpdater
|
||||
class EbitengineService
|
||||
include Updatable
|
||||
include Builds::BuildWeb
|
||||
include Builds::BuildWinX86
|
||||
include Builds::BuildWinX64
|
||||
include Builds::BuildLinuxX64
|
||||
include Builds::BuildMacX64
|
||||
include Builds::BuildMacArm64
|
||||
|
||||
label "Ebitengine"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
module SoftwareUpdater
|
||||
class GodotService
|
||||
include Updatable
|
||||
include Builds::BuildWeb
|
||||
include Builds::BuildWinX86
|
||||
include Builds::BuildWinX64
|
||||
include Builds::BuildLinuxX64
|
||||
include Builds::BuildMacUniversal
|
||||
|
||||
label "Godot"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
module SoftwareUpdater
|
||||
class LoveService
|
||||
include Updatable
|
||||
include Builds::BuildWeb
|
||||
include Builds::BuildWinX64
|
||||
include Builds::BuildLinuxX64
|
||||
include Builds::BuildMacUniversal
|
||||
|
||||
label "LÖVE"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
module SoftwareUpdater
|
||||
class PhaserService
|
||||
include Updatable
|
||||
include Builds::BuildWeb
|
||||
|
||||
label "Phaser"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
module SoftwareUpdater
|
||||
class Tic80Service
|
||||
include Updatable
|
||||
include Builds::BuildCartridge
|
||||
include Builds::BuildSource
|
||||
include Builds::BuildWeb
|
||||
include Builds::BuildDocs
|
||||
include Builds::BuildWinX64
|
||||
include Builds::BuildLinuxX64
|
||||
include Builds::BuildMacX64
|
||||
|
||||
label "TIC-80"
|
||||
|
||||
def cartridge_ext = ".tic"
|
||||
def source_ext = ".lua"
|
||||
|
||||
private
|
||||
|
||||
# a docs opcionális: a régebbi sémájú tic80 pipeline-ok nem töltenek fel
|
||||
# docs zipet, e nélkül is regisztrálni kell a többi assetet
|
||||
def prepare_files(versioned)
|
||||
extract_zip_to_dir("#{versioned}.html.zip", versioned)
|
||||
docs_zip = "#{versioned}-docs.zip"
|
||||
extract_zip_to_dir(docs_zip, "#{versioned}-docs") if File.file?(full_path(docs_zip))
|
||||
end
|
||||
|
||||
def parse_metadata(versioned)
|
||||
parse_lua_metadata(full_path("#{versioned}.lua"))
|
||||
end
|
||||
|
||||
def asset_paths(versioned)
|
||||
paths = {
|
||||
"cartridge" => full_path("#{versioned}.tic"),
|
||||
"source" => full_path("#{versioned}.lua"),
|
||||
"html" => full_path(versioned)
|
||||
}
|
||||
docs_dir = "#{versioned}-docs"
|
||||
paths["docs"] = full_path(docs_dir) if dir_exists?(docs_dir)
|
||||
paths
|
||||
end
|
||||
|
||||
def parse_lua_metadata(source_path)
|
||||
metadata = {}
|
||||
File.foreach(source_path) do |line|
|
||||
|
||||
@@ -15,6 +15,8 @@ Rails.application.routes.draw do
|
||||
get "rss/releases", to: "rss#releases"
|
||||
get "rss/howtos", to: "rss#howtos"
|
||||
get "download", to: "downloads#show"
|
||||
get "builds", to: "builds#index"
|
||||
get "softwares/:name/builds", to: "software_builds#show"
|
||||
end
|
||||
get "update", to: "update#update"
|
||||
get "file/*path", to: "files#show", format: false
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Api::BuildsController, type: :request do
|
||||
describe "GET /api/builds" do
|
||||
it "returns the global build matrix" do
|
||||
get "/api/builds"
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json["platforms"]).to be_a(Hash)
|
||||
expect(json["platforms"]["tic80"]["label"]).to eq("TIC-80")
|
||||
expect(json["platforms"]["tic80"]["kinds"]).to include("cartridge")
|
||||
expect(json["allKinds"]).to be_an(Array)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Api::SoftwareBuildsController, type: :request do
|
||||
describe "GET /api/softwares/:name/builds" do
|
||||
let!(:software) { create(:software, name: "test-game", platform: "love") }
|
||||
let!(:release) { create(:release, software: software, version: "2.0.0") }
|
||||
|
||||
before do
|
||||
ReleaseAsset.create!(release: release, kind: "html", path: "/test/html")
|
||||
end
|
||||
|
||||
it "returns per-software build info" do
|
||||
get "/api/softwares/test-game/builds"
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json["platform"]).to eq("love")
|
||||
expect(json["expected"]).to include("html", "win_x64")
|
||||
expect(json["releases"]["2.0.0"]["actual"]).to include("html")
|
||||
expect(json["releases"]["2.0.0"]["missing"]).to include("win_x64")
|
||||
end
|
||||
|
||||
it "returns 404 for unknown software" do
|
||||
get "/api/softwares/nonexistent/builds"
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe BuildsService do
|
||||
describe "#index" do
|
||||
subject(:result) { described_class.new.index }
|
||||
|
||||
it "returns all supported platforms with expected kinds" do
|
||||
expect(result[:platforms]).to have_key("tic80")
|
||||
expect(result[:platforms]["tic80"][:label]).to eq("TIC-80")
|
||||
expect(result[:platforms]["tic80"][:kinds]).to include("cartridge", "html")
|
||||
end
|
||||
|
||||
it "returns c64 with only cartridge" do
|
||||
expect(result[:platforms]["c64"][:kinds]).to eq(["cartridge"])
|
||||
end
|
||||
|
||||
it "returns allKinds matching ReleaseAsset::KINDS" do
|
||||
expect(result[:allKinds]).to eq(ReleaseAsset::KINDS)
|
||||
end
|
||||
|
||||
it "includes all supported platforms" do
|
||||
UpdateService::SUPPORTED_PLATFORMS.each do |platform|
|
||||
expect(result[:platforms]).to have_key(platform)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#show" do
|
||||
let!(:software) { create(:software, name: "test-game", platform: "love") }
|
||||
let!(:release) { create(:release, software: software, version: "1.0.0") }
|
||||
|
||||
before do
|
||||
ReleaseAsset.create!(release: release, kind: "html", path: "/test/html")
|
||||
ReleaseAsset.create!(release: release, kind: "win_x64", path: "/test/win")
|
||||
end
|
||||
|
||||
it "returns expected and actual kinds per release" do
|
||||
result = described_class.new.show("test-game")
|
||||
|
||||
expect(result[:platform]).to eq("love")
|
||||
expect(result[:expected]).to match_array(%w[html win_x64 linux_x64 mac_universal])
|
||||
expect(result[:releases]["1.0.0"][:actual]).to match_array(%w[html win_x64])
|
||||
expect(result[:releases]["1.0.0"][:missing]).to match_array(%w[linux_x64 mac_universal])
|
||||
end
|
||||
|
||||
it "raises RecordNotFound for unknown software" do
|
||||
expect { described_class.new.show("nonexistent") }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -63,11 +63,17 @@ RSpec.describe SoftwareUpdater::Tic80Service do
|
||||
|
||||
it "registers binary assets when slug zips are present" do
|
||||
write_zip("#{versioned}-win-x64.zip", "game.exe" => "MZ")
|
||||
write_zip("#{versioned}-mac-universal.zip", "game" => "bin")
|
||||
write_zip("#{versioned}-mac-x64.zip", "game" => "bin")
|
||||
|
||||
release = described_class.new.update(name, version)
|
||||
|
||||
expect(asset_kinds(release)).to include("win_x64", "mac_universal")
|
||||
expect(asset_kinds(release)).to include("win_x64", "mac_x64")
|
||||
expect(asset_kinds(release)).not_to include("linux_x64")
|
||||
end
|
||||
|
||||
it "exposes expected_kinds from included Build concerns" do
|
||||
expect(described_class.expected_kinds).to match_array(
|
||||
%w[cartridge source html docs win_x64 linux_x64 mac_x64]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { BuildsData } from '../lib/interfaces/builds.interface'
|
||||
|
||||
const index = async (): Promise<BuildsData> => {
|
||||
const res = await fetch('/api/builds')
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default { index }
|
||||
@@ -179,6 +179,10 @@ export default {
|
||||
new: 'New',
|
||||
read: 'Read',
|
||||
},
|
||||
builds: {
|
||||
title: 'Build Matrix',
|
||||
subtitle: 'Which engines build for which platforms.',
|
||||
},
|
||||
code: {
|
||||
title: 'Codebase',
|
||||
subtitle: 'Explore our collection of open-source projects, study our source code, and contribute to our independent game development tools.',
|
||||
|
||||
@@ -179,6 +179,10 @@ export default {
|
||||
new: 'Új',
|
||||
read: 'Olvasás',
|
||||
},
|
||||
builds: {
|
||||
title: 'Build mátrix',
|
||||
subtitle: 'Melyik engine melyik platformra fordít.',
|
||||
},
|
||||
code: {
|
||||
title: 'Kódbázis',
|
||||
subtitle: 'Fedezd fel nyílt forráskódú projektek gyűjteményét, tanulmányozd forráskódunkat, és járulj hozzá független játékfejlesztő eszközeinkhez.',
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface PlatformBuilds {
|
||||
label: string
|
||||
kinds: string[]
|
||||
}
|
||||
|
||||
export interface BuildsData {
|
||||
platforms: Record<string, PlatformBuilds>
|
||||
allKinds: string[]
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<header class="hero-section-gradient from-slate-700 to-gray-800 py-16">
|
||||
<div class="hero-container">
|
||||
<h1 class="hero-title">{{ t('builds.title') }}</h1>
|
||||
<p class="hero-subtitle text-slate-300">{{ t('builds.subtitle') }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-container py-12 px-4 mt-0">
|
||||
<div v-if="error" class="builds-error">{{ error }}</div>
|
||||
|
||||
<div v-else-if="!data" class="builds-loading">{{ t('common.loading') }}</div>
|
||||
|
||||
<div v-else class="builds-table-wrap">
|
||||
<table class="builds-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="builds-th-engine">Engine</th>
|
||||
<th v-for="kind in data.allKinds" :key="kind" class="builds-th-kind">
|
||||
<i :class="kindIcon(kind)" class="builds-kind-icon"></i>
|
||||
<span class="builds-kind-label">{{ t('catalogShow.assetKind.' + kind) }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(info, platform) in data.platforms" :key="platform" class="builds-row">
|
||||
<td class="builds-td-engine">{{ info.label }}</td>
|
||||
<td v-for="kind in data.allKinds" :key="kind" class="builds-td-kind">
|
||||
<i v-if="info.kinds.includes(kind)" class="fa-solid fa-check builds-check"></i>
|
||||
<i v-else class="fa-solid fa-xmark builds-xmark"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="builds-back">
|
||||
<RouterLink to="/catalog" class="builds-back-link">
|
||||
← {{ t('catalogShow.back') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useBuildsStore } from '../../stores/builds.store'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const store = useBuildsStore()
|
||||
const { data, error } = storeToRefs(store)
|
||||
|
||||
const kindIcon = (kind: string) =>
|
||||
kind.startsWith('win_') ? 'fa-brands fa-windows'
|
||||
: kind.startsWith('linux_') ? 'fa-brands fa-linux'
|
||||
: kind.startsWith('mac_') ? 'fa-brands fa-apple'
|
||||
: kind === 'cartridge' ? 'fa-solid fa-gamepad'
|
||||
: kind === 'source' ? 'fa-solid fa-code'
|
||||
: kind === 'docs' ? 'fa-solid fa-book'
|
||||
: kind === 'html' ? 'fa-solid fa-globe'
|
||||
: 'fa-solid fa-cube'
|
||||
|
||||
onMounted(() => store.fetch())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.builds-error {
|
||||
@apply text-center text-red-500 font-bold py-12;
|
||||
}
|
||||
.builds-loading {
|
||||
@apply text-center text-gray-400 py-12;
|
||||
}
|
||||
.builds-table-wrap {
|
||||
@apply bg-white rounded-2xl shadow-xl border border-gray-100 overflow-x-auto;
|
||||
}
|
||||
.builds-table {
|
||||
@apply w-full text-sm;
|
||||
}
|
||||
.builds-th-engine {
|
||||
@apply px-6 py-4 text-left text-xs font-bold text-gray-400 uppercase tracking-widest bg-gray-50;
|
||||
}
|
||||
.builds-th-kind {
|
||||
@apply px-3 py-4 text-center bg-gray-50;
|
||||
}
|
||||
.builds-kind-icon {
|
||||
@apply block text-gray-500 text-base mb-1;
|
||||
}
|
||||
.builds-kind-label {
|
||||
@apply block text-[10px] font-bold text-gray-400 uppercase tracking-wider leading-tight;
|
||||
}
|
||||
.builds-row {
|
||||
@apply border-t border-gray-100 hover:bg-gray-50/50 transition-colors;
|
||||
}
|
||||
.builds-td-engine {
|
||||
@apply px-6 py-4 font-bold text-gray-900 whitespace-nowrap;
|
||||
}
|
||||
.builds-td-kind {
|
||||
@apply px-3 py-4 text-center;
|
||||
}
|
||||
.builds-check {
|
||||
@apply text-green-500 text-lg;
|
||||
}
|
||||
.builds-xmark {
|
||||
@apply text-gray-200 text-lg;
|
||||
}
|
||||
.builds-back {
|
||||
@apply mt-8 text-center;
|
||||
}
|
||||
.builds-back-link {
|
||||
@apply text-gray-400 hover:text-gray-600 text-sm font-medium transition-colors;
|
||||
}
|
||||
</style>
|
||||
@@ -18,6 +18,12 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="builds-link-row">
|
||||
<RouterLink to="/builds" class="builds-link">
|
||||
<i class="fa-solid fa-table-cells"></i> {{ t('builds.title') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div class="software-list">
|
||||
<template v-for="{ software, releases } in softwares" :key="software.name">
|
||||
<div v-show="activeFilter === 'all' || software.status === activeFilter" class="software-card group">
|
||||
@@ -152,4 +158,10 @@ onMounted(() => store.fetchAll())
|
||||
.software-download-count {
|
||||
@apply text-xs text-gray-400 font-medium ml-1;
|
||||
}
|
||||
.builds-link-row {
|
||||
@apply text-center mb-6;
|
||||
}
|
||||
.builds-link {
|
||||
@apply text-sm text-gray-400 hover:text-purple-600 font-medium transition-colors;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const buildsRouter: RouteRecordRaw[] = [
|
||||
{ path: '/builds', name: 'buildsIndex', component: () => import('../page/builds/BuildsIndexPage.vue') },
|
||||
]
|
||||
@@ -6,6 +6,7 @@ import { codeRouter } from './code.router'
|
||||
import { contactRouter } from './contact.router'
|
||||
import { howtosRouter } from './howtos.router'
|
||||
import { teamRouter } from './team.router'
|
||||
import { buildsRouter } from './builds.router'
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
@@ -17,6 +18,7 @@ export const router = createRouter({
|
||||
...contactRouter,
|
||||
...howtosRouter,
|
||||
...teamRouter,
|
||||
...buildsRouter,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import buildsApi from '../api/builds.api'
|
||||
import { useLoadable } from '../composables/useLoadable'
|
||||
import type { BuildsData } from '../lib/interfaces/builds.interface'
|
||||
|
||||
export const useBuildsStore = defineStore('builds', () => {
|
||||
const data = ref<BuildsData | null>(null)
|
||||
const { loading, error, withCache, invalidate } = useLoadable()
|
||||
|
||||
async function fetch() {
|
||||
await withCache(async () => {
|
||||
data.value = await buildsApi.index()
|
||||
})
|
||||
}
|
||||
|
||||
return { data, loading, error, fetch, invalidate }
|
||||
})
|
||||
Reference in New Issue
Block a user