highlighted
This commit is contained in:
@@ -11,6 +11,7 @@ type Software struct {
|
||||
Site string `gorm:"size:255" json:"site"`
|
||||
License string `gorm:"size:128" json:"license"`
|
||||
Platform string `gorm:"size:128" json:"platform"`
|
||||
Status string `gorm:"size:20;default:development" json:"status"`
|
||||
Releases []Release `json:"-"`
|
||||
Status string `gorm:"size:20;default:development" json:"status"`
|
||||
Highlighted bool `gorm:"default:false" json:"highlighted"`
|
||||
Releases []Release `json:"-"`
|
||||
}
|
||||
|
||||
@@ -6,12 +6,25 @@ type SoftwareRepositoryInterface interface {
|
||||
List() ([]Software, error)
|
||||
GetByName(name string) (*Software, error)
|
||||
UpdateOrCreate(software *Software) error
|
||||
GetHighlighted() (*Software, error)
|
||||
}
|
||||
|
||||
type SoftwareRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (r *SoftwareRepository) GetHighlighted() (*Software, error) {
|
||||
var software Software
|
||||
if err := r.db.Preload("Releases").Where("highlighted = 1").Order("id DESC").First(&software).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &software, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *SoftwareRepository) GetByName(name string) (*Software, error) {
|
||||
var software Software
|
||||
if err := r.db.Preload("Releases").Where("name = ?", name).First(&software).Error; err != nil {
|
||||
|
||||
@@ -27,6 +27,7 @@ type SoftwareServiceInterface interface {
|
||||
GetByName(name string) (*Software, error)
|
||||
GetLatestRelease(software_id string) (*Release, error)
|
||||
GetForShowByName(name string) (*SoftwareShowData, error)
|
||||
GetHighlighted() (*SoftwareShowData, error)
|
||||
}
|
||||
|
||||
type SoftwareService struct {
|
||||
@@ -34,6 +35,18 @@ type SoftwareService struct {
|
||||
release_repository ReleaseRepositoryInterface
|
||||
}
|
||||
|
||||
func (s *SoftwareService) GetHighlighted() (*SoftwareShowData, error) {
|
||||
software, err := s.softeare_repository.GetHighlighted()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if software == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return s.GetForShowByName(software.Name)
|
||||
}
|
||||
|
||||
func NewSoftwareService(
|
||||
softeare_repository SoftwareRepositoryInterface,
|
||||
release_repository ReleaseRepositoryInterface,
|
||||
|
||||
@@ -24,6 +24,7 @@ func replaceReleasePaths(release *domain.Release) {
|
||||
}
|
||||
|
||||
func (c *APISoftwareController) Index(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
softwares, _ := c.softwareService.DetailedList()
|
||||
|
||||
for idx := range softwares.Softwares {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"teletype_softwares/domain"
|
||||
)
|
||||
|
||||
type APISoftwareHighlightedController struct {
|
||||
softwareService domain.SoftwareServiceInterface
|
||||
}
|
||||
|
||||
func NewAPISoftwareHighlightedController(software_service domain.SoftwareServiceInterface) *APISoftwareHighlightedController {
|
||||
return &APISoftwareHighlightedController{softwareService: software_service}
|
||||
}
|
||||
|
||||
func (c *APISoftwareHighlightedController) Index(w http.ResponseWriter, r *http.Request) {
|
||||
softwareShowData, err := c.softwareService.GetHighlighted()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if softwareShowData == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": "no highlighted software found"})
|
||||
return
|
||||
}
|
||||
|
||||
for i := range softwareShowData.Releases {
|
||||
replaceReleasePaths(&softwareShowData.Releases[i])
|
||||
}
|
||||
|
||||
if softwareShowData.LatestRelease != nil {
|
||||
replaceReleasePaths(softwareShowData.LatestRelease)
|
||||
}
|
||||
if softwareShowData.WebPlayableRelease != nil {
|
||||
replaceReleasePaths(softwareShowData.WebPlayableRelease)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(softwareShowData)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
func StartHttpServer(domain_instance domain.Domain) {
|
||||
router := NewRouter(
|
||||
NewAPISoftwareController(domain_instance.SoftwareService),
|
||||
NewAPISoftwareHighlightedController(domain_instance.SoftwareService),
|
||||
NewSoftwareUpdaterController(domain_instance.SoftwareUpdaterService, domain_instance.SoftwareService),
|
||||
).Init()
|
||||
|
||||
|
||||
@@ -7,17 +7,20 @@ import (
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
apiSoftwareController *APISoftwareController
|
||||
softwareUpdaterController *SoftwareUpdaterController
|
||||
apiSoftwareController *APISoftwareController
|
||||
apiSoftwareHighlightedController *APISoftwareHighlightedController
|
||||
softwareUpdaterController *SoftwareUpdaterController
|
||||
}
|
||||
|
||||
func NewRouter(
|
||||
api_software_controller *APISoftwareController,
|
||||
api_software_highlighted_controller *APISoftwareHighlightedController,
|
||||
software_updater_controller *SoftwareUpdaterController,
|
||||
) *Router {
|
||||
return &Router{
|
||||
apiSoftwareController: api_software_controller,
|
||||
softwareUpdaterController: software_updater_controller,
|
||||
apiSoftwareController: api_software_controller,
|
||||
apiSoftwareHighlightedController: api_software_highlighted_controller,
|
||||
softwareUpdaterController: software_updater_controller,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +29,7 @@ func (r *Router) Init() *chi.Mux {
|
||||
router.Use(CORSMiddleware)
|
||||
|
||||
router.Get("/api/software", r.apiSoftwareController.Index)
|
||||
router.Get("/api/software/highlighted", r.apiSoftwareHighlightedController.Index)
|
||||
router.Get("/update", r.softwareUpdaterController.Update)
|
||||
|
||||
fs_file := http.FileServer(http.Dir("/softwares"))
|
||||
|
||||
@@ -63,6 +63,20 @@ try {
|
||||
} catch (e) {
|
||||
console.error('Failed to load events:', e);
|
||||
}
|
||||
|
||||
// Fetch highlighted software
|
||||
const BASE = "https://games.teletype.hu";
|
||||
const API_HIGHLIGHTED = BASE + "/api/software/highlighted";
|
||||
let highlightedSoftware = null;
|
||||
|
||||
try {
|
||||
const res = await fetch(API_HIGHLIGHTED);
|
||||
if (res.ok) {
|
||||
highlightedSoftware = await res.json();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch highlighted software:', e);
|
||||
}
|
||||
---
|
||||
|
||||
<Layout>
|
||||
@@ -115,6 +129,40 @@ try {
|
||||
</section>
|
||||
)}
|
||||
|
||||
<!-- Featured Software Section -->
|
||||
{highlightedSoftware && (
|
||||
<section class="mb-16">
|
||||
<div class="featured-card group">
|
||||
<div class="featured-content">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="featured-badge">Featured Game</span>
|
||||
<span class="status-badge status-released">{highlightedSoftware.software.status}</span>
|
||||
</div>
|
||||
<h2 class="featured-title">{highlightedSoftware.software.title}</h2>
|
||||
<p class="featured-desc">{highlightedSoftware.software.desc}</p>
|
||||
|
||||
<div class="flex flex-wrap gap-4 mt-8">
|
||||
{highlightedSoftware.webPlayableRelease && (
|
||||
<a href={BASE + highlightedSoftware.webPlayableRelease.htmlFolderPath} target="_blank" class="featured-btn-play">
|
||||
▶ Play in Browser
|
||||
</a>
|
||||
)}
|
||||
<a href="/catalog" class="featured-btn-secondary">
|
||||
View in Catalog
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="featured-visual">
|
||||
<div class="featured-placeholder">
|
||||
<span class="text-8-xl opacity-20 group-hover:scale-110 transition-transform duration-500">🎮</span>
|
||||
<div class="featured-glow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<!-- Navigation Grid -->
|
||||
<section class="card-grid">
|
||||
<!-- Catalog Section -->
|
||||
@@ -171,7 +219,96 @@ try {
|
||||
.home-header-decor {
|
||||
@apply absolute inset-0 opacity-30;
|
||||
}
|
||||
|
||||
|
||||
/* Featured Card */
|
||||
.featured-card {
|
||||
@apply relative overflow-hidden bg-gradient-to-br from-indigo-900 to-slate-900 rounded-3xl shadow-2xl flex flex-col md:flex-row min-h-[400px];
|
||||
}
|
||||
.featured-content {
|
||||
@apply p-8 md:p-12 flex flex-col justify-center flex-1 z-10;
|
||||
}
|
||||
.featured-badge {
|
||||
@apply px-3 py-1 bg-indigo-500/20 text-indigo-300 border border-indigo-400/30 rounded-full text-xs font-bold uppercase tracking-wider;
|
||||
}
|
||||
.featured-title {
|
||||
@apply text-4xl md:text-5xl font-black text-white mb-6;
|
||||
}
|
||||
.featured-desc {
|
||||
@apply text-indigo-100/80 text-lg leading-relaxed max-w-xl;
|
||||
}
|
||||
.featured-btn-play {
|
||||
@apply px-8 py-4 bg-white text-indigo-900 font-bold rounded-xl transition-all hover:scale-105 hover:shadow-xl hover:shadow-indigo-500/20 active:scale-95;
|
||||
}
|
||||
.featured-btn-secondary {
|
||||
@apply px-8 py-4 bg-indigo-500/10 text-white border border-indigo-400/30 font-bold rounded-xl transition-all hover:bg-indigo-500/20;
|
||||
}
|
||||
.featured-visual {
|
||||
@apply flex-1 relative min-h-[300px] md:min-h-full overflow-hidden;
|
||||
}
|
||||
.featured-placeholder {
|
||||
@apply absolute inset-0 flex items-center justify-center bg-indigo-500/5;
|
||||
}
|
||||
.featured-glow {
|
||||
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-indigo-500/30 rounded-full blur-[100px] pointer-events-none;
|
||||
}
|
||||
.text-8-xl {
|
||||
font-size: 8rem;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.status-badge {
|
||||
@apply px-2 py-0.5 rounded text-[10px] font-bold uppercase;
|
||||
}
|
||||
.status-released { @apply bg-green-500/20 text-green-300 border border-green-500/30; }
|
||||
.status-demo { @apply bg-blue-500/20 text-blue-300 border border-blue-500/30; }
|
||||
.status-development { @apply bg-yellow-500/20 text-yellow-300 border border-yellow-500/30; }
|
||||
.status-archived { @apply bg-gray-500/20 text-gray-300 border border-gray-500/30; }
|
||||
|
||||
/* Countdown Card */
|
||||
|
||||
.featured-card {
|
||||
@apply relative overflow-hidden bg-gradient-to-br from-indigo-900 to-slate-900 rounded-3xl shadow-2xl flex flex-col md:flex-row min-h-[400px];
|
||||
}
|
||||
.featured-content {
|
||||
@apply p-8 md:p-12 flex flex-col justify-center flex-1 z-10;
|
||||
}
|
||||
.featured-badge {
|
||||
@apply px-3 py-1 bg-indigo-500/20 text-indigo-300 border border-indigo-400/30 rounded-full text-xs font-bold uppercase tracking-wider;
|
||||
}
|
||||
.featured-title {
|
||||
@apply text-4xl md:text-5xl font-black text-white mb-6;
|
||||
}
|
||||
.featured-desc {
|
||||
@apply text-indigo-100/80 text-lg leading-relaxed max-w-xl;
|
||||
}
|
||||
.featured-btn-play {
|
||||
@apply px-8 py-4 bg-white text-indigo-900 font-bold rounded-xl transition-all hover:scale-105 hover:shadow-xl hover:shadow-indigo-500/20 active:scale-95;
|
||||
}
|
||||
.featured-btn-secondary {
|
||||
@apply px-8 py-4 bg-indigo-500/10 text-white border border-indigo-400/30 font-bold rounded-xl transition-all hover:bg-indigo-500/20;
|
||||
}
|
||||
.featured-visual {
|
||||
@apply flex-1 relative min-h-[300px] md:min-h-full overflow-hidden;
|
||||
}
|
||||
.featured-placeholder {
|
||||
@apply absolute inset-0 flex items-center justify-center bg-indigo-500/5;
|
||||
}
|
||||
.featured-glow {
|
||||
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-indigo-500/30 rounded-full blur-[100px] pointer-events-none;
|
||||
}
|
||||
.text-8-xl {
|
||||
font-size: 8rem;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.status-badge {
|
||||
@apply px-2 py-0.5 rounded text-[10px] font-bold uppercase;
|
||||
}
|
||||
.status-released { @apply bg-green-500/20 text-green-300 border border-green-500/30; }
|
||||
.status-demo { @apply bg-blue-500/20 text-blue-300 border border-blue-500/30; }
|
||||
.status-development { @apply bg-yellow-500/20 text-yellow-300 border border-yellow-500/30; }
|
||||
.status-archived { @apply bg-gray-500/20 text-gray-300 border border-gray-500/30; }
|
||||
|
||||
/* Countdown Card */
|
||||
.countdown-card {
|
||||
@apply bg-white rounded-2xl shadow-xl overflow-hidden border border-gray-100 p-8 md:p-12 text-center transition-all duration-300;
|
||||
|
||||
Reference in New Issue
Block a user