From 66fd78c10fbc75f62a74e6e86fa68eac956f6f2d Mon Sep 17 00:00:00 2001 From: ghassan Date: Fri, 22 May 2026 21:32:52 +0300 Subject: [PATCH] Add multi-language audio tracks and self-hosted flag-icons Introduce per-video language support and multiple audio tracks (VideoAudioTrack model + migrations for language, description, title), a reusable language-select component, and a track-editor form. Bundle the self-hosted flag-icons v7.2.3 library and a NAS auto-sync command. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/component-usage.md | 63 +- .gitignore | 1 + CLAUDE.md | 33 +- app/Console/Commands/NasAutoSync.php | 75 + app/Console/Kernel.php | 19 +- app/Data/Countries.php | 8 +- app/Data/Languages.php | 152 + app/Http/Controllers/SuperAdminController.php | 1 + app/Http/Controllers/UserController.php | 34 +- app/Http/Controllers/VideoController.php | 625 ++++- app/Jobs/NasToLocalMigrationJob.php | 1 + app/Models/Video.php | 6 + app/Models/VideoAudioTrack.php | 27 + app/Services/NasSyncService.php | 146 +- ...000001_create_video_audio_tracks_table.php | 28 + ...20_000002_add_language_to_videos_table.php | 22 + ...escription_to_video_audio_tracks_table.php | 25 + ..._add_title_to_video_audio_tracks_table.php | 22 + .../vendor/flag-icons/css/flag-icons.min.css | 1 + public/vendor/flag-icons/flags/4x3/ad.svg | 150 + public/vendor/flag-icons/flags/4x3/ae.svg | 6 + public/vendor/flag-icons/flags/4x3/af.svg | 81 + public/vendor/flag-icons/flags/4x3/ag.svg | 14 + public/vendor/flag-icons/flags/4x3/ai.svg | 29 + public/vendor/flag-icons/flags/4x3/al.svg | 5 + public/vendor/flag-icons/flags/4x3/am.svg | 5 + public/vendor/flag-icons/flags/4x3/ao.svg | 13 + public/vendor/flag-icons/flags/4x3/aq.svg | 5 + public/vendor/flag-icons/flags/4x3/ar.svg | 32 + public/vendor/flag-icons/flags/4x3/arab.svg | 109 + public/vendor/flag-icons/flags/4x3/as.svg | 72 + public/vendor/flag-icons/flags/4x3/at.svg | 4 + public/vendor/flag-icons/flags/4x3/au.svg | 8 + public/vendor/flag-icons/flags/4x3/aw.svg | 186 ++ public/vendor/flag-icons/flags/4x3/ax.svg | 18 + public/vendor/flag-icons/flags/4x3/az.svg | 8 + public/vendor/flag-icons/flags/4x3/ba.svg | 12 + public/vendor/flag-icons/flags/4x3/bb.svg | 6 + public/vendor/flag-icons/flags/4x3/bd.svg | 4 + public/vendor/flag-icons/flags/4x3/be.svg | 7 + public/vendor/flag-icons/flags/4x3/bf.svg | 7 + public/vendor/flag-icons/flags/4x3/bg.svg | 5 + public/vendor/flag-icons/flags/4x3/bh.svg | 4 + public/vendor/flag-icons/flags/4x3/bi.svg | 15 + public/vendor/flag-icons/flags/4x3/bj.svg | 14 + public/vendor/flag-icons/flags/4x3/bl.svg | 5 + public/vendor/flag-icons/flags/4x3/bm.svg | 97 + public/vendor/flag-icons/flags/4x3/bn.svg | 36 + public/vendor/flag-icons/flags/4x3/bo.svg | 674 +++++ public/vendor/flag-icons/flags/4x3/bq.svg | 5 + public/vendor/flag-icons/flags/4x3/br.svg | 45 + public/vendor/flag-icons/flags/4x3/bs.svg | 13 + public/vendor/flag-icons/flags/4x3/bt.svg | 89 + public/vendor/flag-icons/flags/4x3/bv.svg | 13 + public/vendor/flag-icons/flags/4x3/bw.svg | 7 + public/vendor/flag-icons/flags/4x3/by.svg | 18 + public/vendor/flag-icons/flags/4x3/bz.svg | 145 + public/vendor/flag-icons/flags/4x3/ca.svg | 4 + public/vendor/flag-icons/flags/4x3/cc.svg | 19 + public/vendor/flag-icons/flags/4x3/cd.svg | 5 + public/vendor/flag-icons/flags/4x3/cefta.svg | 13 + public/vendor/flag-icons/flags/4x3/cf.svg | 15 + public/vendor/flag-icons/flags/4x3/cg.svg | 12 + public/vendor/flag-icons/flags/4x3/ch.svg | 9 + public/vendor/flag-icons/flags/4x3/ci.svg | 7 + public/vendor/flag-icons/flags/4x3/ck.svg | 9 + public/vendor/flag-icons/flags/4x3/cl.svg | 13 + public/vendor/flag-icons/flags/4x3/cm.svg | 15 + public/vendor/flag-icons/flags/4x3/cn.svg | 11 + public/vendor/flag-icons/flags/4x3/co.svg | 7 + public/vendor/flag-icons/flags/4x3/cp.svg | 7 + public/vendor/flag-icons/flags/4x3/cr.svg | 7 + public/vendor/flag-icons/flags/4x3/cu.svg | 13 + public/vendor/flag-icons/flags/4x3/cv.svg | 13 + public/vendor/flag-icons/flags/4x3/cw.svg | 14 + public/vendor/flag-icons/flags/4x3/cx.svg | 15 + public/vendor/flag-icons/flags/4x3/cy.svg | 6 + public/vendor/flag-icons/flags/4x3/cz.svg | 5 + public/vendor/flag-icons/flags/4x3/de.svg | 5 + public/vendor/flag-icons/flags/4x3/dg.svg | 130 + public/vendor/flag-icons/flags/4x3/dj.svg | 13 + public/vendor/flag-icons/flags/4x3/dk.svg | 5 + public/vendor/flag-icons/flags/4x3/dm.svg | 152 + public/vendor/flag-icons/flags/4x3/do.svg | 121 + public/vendor/flag-icons/flags/4x3/dz.svg | 5 + public/vendor/flag-icons/flags/4x3/eac.svg | 48 + public/vendor/flag-icons/flags/4x3/ec.svg | 138 + public/vendor/flag-icons/flags/4x3/ee.svg | 5 + public/vendor/flag-icons/flags/4x3/eg.svg | 38 + public/vendor/flag-icons/flags/4x3/eh.svg | 16 + public/vendor/flag-icons/flags/4x3/er.svg | 8 + public/vendor/flag-icons/flags/4x3/es-ct.svg | 4 + public/vendor/flag-icons/flags/4x3/es-ga.svg | 187 ++ public/vendor/flag-icons/flags/4x3/es-pv.svg | 5 + public/vendor/flag-icons/flags/4x3/es.svg | 544 ++++ public/vendor/flag-icons/flags/4x3/et.svg | 14 + public/vendor/flag-icons/flags/4x3/eu.svg | 28 + public/vendor/flag-icons/flags/4x3/fi.svg | 5 + public/vendor/flag-icons/flags/4x3/fj.svg | 120 + public/vendor/flag-icons/flags/4x3/fk.svg | 90 + public/vendor/flag-icons/flags/4x3/fm.svg | 11 + public/vendor/flag-icons/flags/4x3/fo.svg | 12 + public/vendor/flag-icons/flags/4x3/fr.svg | 5 + public/vendor/flag-icons/flags/4x3/ga.svg | 7 + public/vendor/flag-icons/flags/4x3/gb-eng.svg | 5 + public/vendor/flag-icons/flags/4x3/gb-nir.svg | 132 + public/vendor/flag-icons/flags/4x3/gb-sct.svg | 4 + public/vendor/flag-icons/flags/4x3/gb-wls.svg | 9 + public/vendor/flag-icons/flags/4x3/gb.svg | 7 + public/vendor/flag-icons/flags/4x3/gd.svg | 27 + public/vendor/flag-icons/flags/4x3/ge.svg | 6 + public/vendor/flag-icons/flags/4x3/gf.svg | 5 + public/vendor/flag-icons/flags/4x3/gg.svg | 9 + public/vendor/flag-icons/flags/4x3/gh.svg | 6 + public/vendor/flag-icons/flags/4x3/gi.svg | 32 + public/vendor/flag-icons/flags/4x3/gl.svg | 4 + public/vendor/flag-icons/flags/4x3/gm.svg | 14 + public/vendor/flag-icons/flags/4x3/gn.svg | 7 + public/vendor/flag-icons/flags/4x3/gp.svg | 5 + public/vendor/flag-icons/flags/4x3/gq.svg | 23 + public/vendor/flag-icons/flags/4x3/gr.svg | 16 + public/vendor/flag-icons/flags/4x3/gs.svg | 133 + public/vendor/flag-icons/flags/4x3/gt.svg | 204 ++ public/vendor/flag-icons/flags/4x3/gu.svg | 23 + public/vendor/flag-icons/flags/4x3/gw.svg | 13 + public/vendor/flag-icons/flags/4x3/gy.svg | 9 + public/vendor/flag-icons/flags/4x3/hk.svg | 8 + public/vendor/flag-icons/flags/4x3/hm.svg | 8 + public/vendor/flag-icons/flags/4x3/hn.svg | 18 + public/vendor/flag-icons/flags/4x3/hr.svg | 58 + public/vendor/flag-icons/flags/4x3/ht.svg | 116 + public/vendor/flag-icons/flags/4x3/hu.svg | 7 + public/vendor/flag-icons/flags/4x3/ic.svg | 7 + public/vendor/flag-icons/flags/4x3/id.svg | 4 + public/vendor/flag-icons/flags/4x3/ie.svg | 7 + public/vendor/flag-icons/flags/4x3/il.svg | 14 + public/vendor/flag-icons/flags/4x3/im.svg | 36 + public/vendor/flag-icons/flags/4x3/in.svg | 25 + public/vendor/flag-icons/flags/4x3/io.svg | 130 + public/vendor/flag-icons/flags/4x3/iq.svg | 10 + public/vendor/flag-icons/flags/4x3/ir.svg | 219 ++ public/vendor/flag-icons/flags/4x3/is.svg | 12 + public/vendor/flag-icons/flags/4x3/it.svg | 7 + public/vendor/flag-icons/flags/4x3/je.svg | 62 + public/vendor/flag-icons/flags/4x3/jm.svg | 8 + public/vendor/flag-icons/flags/4x3/jo.svg | 16 + public/vendor/flag-icons/flags/4x3/jp.svg | 11 + public/vendor/flag-icons/flags/4x3/ke.svg | 23 + public/vendor/flag-icons/flags/4x3/kg.svg | 15 + public/vendor/flag-icons/flags/4x3/kh.svg | 61 + public/vendor/flag-icons/flags/4x3/ki.svg | 36 + public/vendor/flag-icons/flags/4x3/km.svg | 16 + public/vendor/flag-icons/flags/4x3/kn.svg | 14 + public/vendor/flag-icons/flags/4x3/kp.svg | 15 + public/vendor/flag-icons/flags/4x3/kr.svg | 24 + public/vendor/flag-icons/flags/4x3/kw.svg | 13 + public/vendor/flag-icons/flags/4x3/ky.svg | 103 + public/vendor/flag-icons/flags/4x3/kz.svg | 36 + public/vendor/flag-icons/flags/4x3/la.svg | 12 + public/vendor/flag-icons/flags/4x3/lb.svg | 15 + public/vendor/flag-icons/flags/4x3/lc.svg | 8 + public/vendor/flag-icons/flags/4x3/li.svg | 43 + public/vendor/flag-icons/flags/4x3/lk.svg | 22 + public/vendor/flag-icons/flags/4x3/lr.svg | 14 + public/vendor/flag-icons/flags/4x3/ls.svg | 8 + public/vendor/flag-icons/flags/4x3/lt.svg | 7 + public/vendor/flag-icons/flags/4x3/lu.svg | 5 + public/vendor/flag-icons/flags/4x3/lv.svg | 6 + public/vendor/flag-icons/flags/4x3/ly.svg | 13 + public/vendor/flag-icons/flags/4x3/ma.svg | 4 + public/vendor/flag-icons/flags/4x3/mc.svg | 6 + public/vendor/flag-icons/flags/4x3/md.svg | 70 + public/vendor/flag-icons/flags/4x3/me.svg | 116 + public/vendor/flag-icons/flags/4x3/mf.svg | 5 + public/vendor/flag-icons/flags/4x3/mg.svg | 7 + public/vendor/flag-icons/flags/4x3/mh.svg | 7 + public/vendor/flag-icons/flags/4x3/mk.svg | 5 + public/vendor/flag-icons/flags/4x3/ml.svg | 7 + public/vendor/flag-icons/flags/4x3/mm.svg | 12 + public/vendor/flag-icons/flags/4x3/mn.svg | 14 + public/vendor/flag-icons/flags/4x3/mo.svg | 9 + public/vendor/flag-icons/flags/4x3/mp.svg | 86 + public/vendor/flag-icons/flags/4x3/mq.svg | 5 + public/vendor/flag-icons/flags/4x3/mr.svg | 6 + public/vendor/flag-icons/flags/4x3/ms.svg | 29 + public/vendor/flag-icons/flags/4x3/mt.svg | 58 + public/vendor/flag-icons/flags/4x3/mu.svg | 8 + public/vendor/flag-icons/flags/4x3/mv.svg | 6 + public/vendor/flag-icons/flags/4x3/mw.svg | 10 + public/vendor/flag-icons/flags/4x3/mx.svg | 382 +++ public/vendor/flag-icons/flags/4x3/my.svg | 26 + public/vendor/flag-icons/flags/4x3/mz.svg | 21 + public/vendor/flag-icons/flags/4x3/na.svg | 16 + public/vendor/flag-icons/flags/4x3/nc.svg | 13 + public/vendor/flag-icons/flags/4x3/ne.svg | 6 + public/vendor/flag-icons/flags/4x3/nf.svg | 9 + public/vendor/flag-icons/flags/4x3/ng.svg | 6 + public/vendor/flag-icons/flags/4x3/ni.svg | 129 + public/vendor/flag-icons/flags/4x3/nl.svg | 5 + public/vendor/flag-icons/flags/4x3/no.svg | 7 + public/vendor/flag-icons/flags/4x3/np.svg | 13 + public/vendor/flag-icons/flags/4x3/nr.svg | 12 + public/vendor/flag-icons/flags/4x3/nu.svg | 10 + public/vendor/flag-icons/flags/4x3/nz.svg | 36 + public/vendor/flag-icons/flags/4x3/om.svg | 115 + public/vendor/flag-icons/flags/4x3/pa.svg | 14 + public/vendor/flag-icons/flags/4x3/pc.svg | 33 + public/vendor/flag-icons/flags/4x3/pe.svg | 4 + public/vendor/flag-icons/flags/4x3/pf.svg | 19 + public/vendor/flag-icons/flags/4x3/pg.svg | 9 + public/vendor/flag-icons/flags/4x3/ph.svg | 6 + public/vendor/flag-icons/flags/4x3/pk.svg | 15 + public/vendor/flag-icons/flags/4x3/pl.svg | 6 + public/vendor/flag-icons/flags/4x3/pm.svg | 5 + public/vendor/flag-icons/flags/4x3/pn.svg | 53 + public/vendor/flag-icons/flags/4x3/pr.svg | 13 + public/vendor/flag-icons/flags/4x3/ps.svg | 15 + public/vendor/flag-icons/flags/4x3/pt.svg | 57 + public/vendor/flag-icons/flags/4x3/pw.svg | 11 + public/vendor/flag-icons/flags/4x3/py.svg | 157 ++ public/vendor/flag-icons/flags/4x3/qa.svg | 4 + public/vendor/flag-icons/flags/4x3/re.svg | 5 + public/vendor/flag-icons/flags/4x3/ro.svg | 7 + public/vendor/flag-icons/flags/4x3/rs.svg | 292 ++ public/vendor/flag-icons/flags/4x3/ru.svg | 5 + public/vendor/flag-icons/flags/4x3/rw.svg | 13 + public/vendor/flag-icons/flags/4x3/sa.svg | 25 + public/vendor/flag-icons/flags/4x3/sb.svg | 13 + public/vendor/flag-icons/flags/4x3/sc.svg | 7 + public/vendor/flag-icons/flags/4x3/sd.svg | 13 + public/vendor/flag-icons/flags/4x3/se.svg | 4 + public/vendor/flag-icons/flags/4x3/sg.svg | 13 + public/vendor/flag-icons/flags/4x3/sh-ac.svg | 689 +++++ public/vendor/flag-icons/flags/4x3/sh-hl.svg | 164 ++ public/vendor/flag-icons/flags/4x3/sh-ta.svg | 76 + public/vendor/flag-icons/flags/4x3/sh.svg | 7 + public/vendor/flag-icons/flags/4x3/si.svg | 18 + public/vendor/flag-icons/flags/4x3/sj.svg | 7 + public/vendor/flag-icons/flags/4x3/sk.svg | 9 + public/vendor/flag-icons/flags/4x3/sl.svg | 7 + public/vendor/flag-icons/flags/4x3/sm.svg | 75 + public/vendor/flag-icons/flags/4x3/sn.svg | 8 + public/vendor/flag-icons/flags/4x3/so.svg | 11 + public/vendor/flag-icons/flags/4x3/sr.svg | 6 + public/vendor/flag-icons/flags/4x3/ss.svg | 8 + public/vendor/flag-icons/flags/4x3/st.svg | 16 + public/vendor/flag-icons/flags/4x3/sv.svg | 594 ++++ public/vendor/flag-icons/flags/4x3/sx.svg | 56 + public/vendor/flag-icons/flags/4x3/sy.svg | 6 + public/vendor/flag-icons/flags/4x3/sz.svg | 34 + public/vendor/flag-icons/flags/4x3/tc.svg | 50 + public/vendor/flag-icons/flags/4x3/td.svg | 7 + public/vendor/flag-icons/flags/4x3/tf.svg | 15 + public/vendor/flag-icons/flags/4x3/tg.svg | 14 + public/vendor/flag-icons/flags/4x3/th.svg | 7 + public/vendor/flag-icons/flags/4x3/tj.svg | 22 + public/vendor/flag-icons/flags/4x3/tk.svg | 5 + public/vendor/flag-icons/flags/4x3/tl.svg | 13 + public/vendor/flag-icons/flags/4x3/tm.svg | 204 ++ public/vendor/flag-icons/flags/4x3/tn.svg | 4 + public/vendor/flag-icons/flags/4x3/to.svg | 10 + public/vendor/flag-icons/flags/4x3/tr.svg | 8 + public/vendor/flag-icons/flags/4x3/tt.svg | 5 + public/vendor/flag-icons/flags/4x3/tv.svg | 9 + public/vendor/flag-icons/flags/4x3/tw.svg | 34 + public/vendor/flag-icons/flags/4x3/tz.svg | 13 + public/vendor/flag-icons/flags/4x3/ua.svg | 6 + public/vendor/flag-icons/flags/4x3/ug.svg | 30 + public/vendor/flag-icons/flags/4x3/um.svg | 9 + public/vendor/flag-icons/flags/4x3/un.svg | 16 + public/vendor/flag-icons/flags/4x3/us.svg | 9 + public/vendor/flag-icons/flags/4x3/uy.svg | 28 + public/vendor/flag-icons/flags/4x3/uz.svg | 30 + public/vendor/flag-icons/flags/4x3/va.svg | 190 ++ public/vendor/flag-icons/flags/4x3/vc.svg | 8 + public/vendor/flag-icons/flags/4x3/ve.svg | 26 + public/vendor/flag-icons/flags/4x3/vg.svg | 59 + public/vendor/flag-icons/flags/4x3/vi.svg | 28 + public/vendor/flag-icons/flags/4x3/vn.svg | 11 + public/vendor/flag-icons/flags/4x3/vu.svg | 21 + public/vendor/flag-icons/flags/4x3/wf.svg | 5 + public/vendor/flag-icons/flags/4x3/ws.svg | 7 + public/vendor/flag-icons/flags/4x3/xk.svg | 5 + public/vendor/flag-icons/flags/4x3/xx.svg | 4 + public/vendor/flag-icons/flags/4x3/ye.svg | 7 + public/vendor/flag-icons/flags/4x3/yt.svg | 5 + public/vendor/flag-icons/flags/4x3/za.svg | 17 + public/vendor/flag-icons/flags/4x3/zm.svg | 27 + public/vendor/flag-icons/flags/4x3/zw.svg | 21 + resources/views/admin/dashboard.blade.php | 28 +- resources/views/admin/layout.blade.php | 1 + .../views/admin/video-analytics.blade.php | 12 +- .../views/components/country-select.blade.php | 13 +- .../components/language-select.blade.php | 242 ++ .../components/phone-code-select.blade.php | 13 +- .../views/components/playlist-card.blade.php | 14 +- .../components/timezone-select.blade.php | 13 +- .../components/track-editor-form.blade.php | 376 +++ .../views/components/video-actions.blade.php | 4 +- .../views/components/video-card.blade.php | 21 + .../views/components/video-insights.blade.php | 142 +- .../views/components/video-player.blade.php | 87 +- resources/views/layouts/app.blade.php | 896 +++--- .../partials/edit-video-modal.blade.php | 2442 +++++++---------- .../layouts/partials/upload-modal.blade.php | 2042 ++++++++------ resources/views/user/channel.blade.php | 8 +- resources/views/videos/create.blade.php | 581 +++- resources/views/videos/edit.blade.php | 136 + .../videos/partials/audio-player.blade.php | 394 ++- .../videos/partials/description-box.blade.php | 6 +- .../views/videos/types/generic.blade.php | 41 +- resources/views/videos/types/match.blade.php | 60 +- resources/views/videos/types/music.blade.php | 55 +- routes/web.php | 3 + 314 files changed, 16507 insertions(+), 3172 deletions(-) create mode 100644 app/Console/Commands/NasAutoSync.php create mode 100644 app/Data/Languages.php create mode 100644 app/Models/VideoAudioTrack.php create mode 100644 database/migrations/2026_05_20_000001_create_video_audio_tracks_table.php create mode 100644 database/migrations/2026_05_20_000002_add_language_to_videos_table.php create mode 100644 database/migrations/2026_05_21_142548_add_description_to_video_audio_tracks_table.php create mode 100644 database/migrations/2026_05_22_094614_add_title_to_video_audio_tracks_table.php create mode 100644 public/vendor/flag-icons/css/flag-icons.min.css create mode 100644 public/vendor/flag-icons/flags/4x3/ad.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ae.svg create mode 100644 public/vendor/flag-icons/flags/4x3/af.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ag.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ai.svg create mode 100644 public/vendor/flag-icons/flags/4x3/al.svg create mode 100644 public/vendor/flag-icons/flags/4x3/am.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ao.svg create mode 100644 public/vendor/flag-icons/flags/4x3/aq.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ar.svg create mode 100644 public/vendor/flag-icons/flags/4x3/arab.svg create mode 100644 public/vendor/flag-icons/flags/4x3/as.svg create mode 100644 public/vendor/flag-icons/flags/4x3/at.svg create mode 100644 public/vendor/flag-icons/flags/4x3/au.svg create mode 100644 public/vendor/flag-icons/flags/4x3/aw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ax.svg create mode 100644 public/vendor/flag-icons/flags/4x3/az.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ba.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bb.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bd.svg create mode 100644 public/vendor/flag-icons/flags/4x3/be.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bh.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bi.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bj.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bo.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bq.svg create mode 100644 public/vendor/flag-icons/flags/4x3/br.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bs.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/by.svg create mode 100644 public/vendor/flag-icons/flags/4x3/bz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ca.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cd.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cefta.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ch.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ci.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ck.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/co.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cp.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cx.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cy.svg create mode 100644 public/vendor/flag-icons/flags/4x3/cz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/de.svg create mode 100644 public/vendor/flag-icons/flags/4x3/dg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/dj.svg create mode 100644 public/vendor/flag-icons/flags/4x3/dk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/dm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/do.svg create mode 100644 public/vendor/flag-icons/flags/4x3/dz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/eac.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ec.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ee.svg create mode 100644 public/vendor/flag-icons/flags/4x3/eg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/eh.svg create mode 100644 public/vendor/flag-icons/flags/4x3/er.svg create mode 100644 public/vendor/flag-icons/flags/4x3/es-ct.svg create mode 100644 public/vendor/flag-icons/flags/4x3/es-ga.svg create mode 100644 public/vendor/flag-icons/flags/4x3/es-pv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/es.svg create mode 100644 public/vendor/flag-icons/flags/4x3/et.svg create mode 100644 public/vendor/flag-icons/flags/4x3/eu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/fi.svg create mode 100644 public/vendor/flag-icons/flags/4x3/fj.svg create mode 100644 public/vendor/flag-icons/flags/4x3/fk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/fm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/fo.svg create mode 100644 public/vendor/flag-icons/flags/4x3/fr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ga.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gb-eng.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gb-nir.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gb-sct.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gb-wls.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gb.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gd.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ge.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gh.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gi.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gp.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gq.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gs.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/gy.svg create mode 100644 public/vendor/flag-icons/flags/4x3/hk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/hm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/hn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/hr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ht.svg create mode 100644 public/vendor/flag-icons/flags/4x3/hu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ic.svg create mode 100644 public/vendor/flag-icons/flags/4x3/id.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ie.svg create mode 100644 public/vendor/flag-icons/flags/4x3/il.svg create mode 100644 public/vendor/flag-icons/flags/4x3/im.svg create mode 100644 public/vendor/flag-icons/flags/4x3/in.svg create mode 100644 public/vendor/flag-icons/flags/4x3/io.svg create mode 100644 public/vendor/flag-icons/flags/4x3/iq.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ir.svg create mode 100644 public/vendor/flag-icons/flags/4x3/is.svg create mode 100644 public/vendor/flag-icons/flags/4x3/it.svg create mode 100644 public/vendor/flag-icons/flags/4x3/je.svg create mode 100644 public/vendor/flag-icons/flags/4x3/jm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/jo.svg create mode 100644 public/vendor/flag-icons/flags/4x3/jp.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ke.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kh.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ki.svg create mode 100644 public/vendor/flag-icons/flags/4x3/km.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kp.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ky.svg create mode 100644 public/vendor/flag-icons/flags/4x3/kz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/la.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lb.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/li.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ls.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/lv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ly.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ma.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/md.svg create mode 100644 public/vendor/flag-icons/flags/4x3/me.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mh.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ml.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mo.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mp.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mq.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ms.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mx.svg create mode 100644 public/vendor/flag-icons/flags/4x3/my.svg create mode 100644 public/vendor/flag-icons/flags/4x3/mz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/na.svg create mode 100644 public/vendor/flag-icons/flags/4x3/nc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ne.svg create mode 100644 public/vendor/flag-icons/flags/4x3/nf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ng.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ni.svg create mode 100644 public/vendor/flag-icons/flags/4x3/nl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/no.svg create mode 100644 public/vendor/flag-icons/flags/4x3/np.svg create mode 100644 public/vendor/flag-icons/flags/4x3/nr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/nu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/nz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/om.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pa.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pe.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ph.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ps.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/pw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/py.svg create mode 100644 public/vendor/flag-icons/flags/4x3/qa.svg create mode 100644 public/vendor/flag-icons/flags/4x3/re.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ro.svg create mode 100644 public/vendor/flag-icons/flags/4x3/rs.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ru.svg create mode 100644 public/vendor/flag-icons/flags/4x3/rw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sa.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sb.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sd.svg create mode 100644 public/vendor/flag-icons/flags/4x3/se.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sh-ac.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sh-hl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sh-ta.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sh.svg create mode 100644 public/vendor/flag-icons/flags/4x3/si.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sj.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/so.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ss.svg create mode 100644 public/vendor/flag-icons/flags/4x3/st.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sx.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sy.svg create mode 100644 public/vendor/flag-icons/flags/4x3/sz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/td.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/th.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tj.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tl.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/to.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tr.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tv.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tw.svg create mode 100644 public/vendor/flag-icons/flags/4x3/tz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ua.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ug.svg create mode 100644 public/vendor/flag-icons/flags/4x3/um.svg create mode 100644 public/vendor/flag-icons/flags/4x3/un.svg create mode 100644 public/vendor/flag-icons/flags/4x3/us.svg create mode 100644 public/vendor/flag-icons/flags/4x3/uy.svg create mode 100644 public/vendor/flag-icons/flags/4x3/uz.svg create mode 100644 public/vendor/flag-icons/flags/4x3/va.svg create mode 100644 public/vendor/flag-icons/flags/4x3/vc.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ve.svg create mode 100644 public/vendor/flag-icons/flags/4x3/vg.svg create mode 100644 public/vendor/flag-icons/flags/4x3/vi.svg create mode 100644 public/vendor/flag-icons/flags/4x3/vn.svg create mode 100644 public/vendor/flag-icons/flags/4x3/vu.svg create mode 100644 public/vendor/flag-icons/flags/4x3/wf.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ws.svg create mode 100644 public/vendor/flag-icons/flags/4x3/xk.svg create mode 100644 public/vendor/flag-icons/flags/4x3/xx.svg create mode 100644 public/vendor/flag-icons/flags/4x3/ye.svg create mode 100644 public/vendor/flag-icons/flags/4x3/yt.svg create mode 100644 public/vendor/flag-icons/flags/4x3/za.svg create mode 100644 public/vendor/flag-icons/flags/4x3/zm.svg create mode 100644 public/vendor/flag-icons/flags/4x3/zw.svg create mode 100644 resources/views/components/language-select.blade.php create mode 100644 resources/views/components/track-editor-form.blade.php diff --git a/.claude/component-usage.md b/.claude/component-usage.md index dcab428..8ae6a14 100644 --- a/.claude/component-usage.md +++ b/.claude/component-usage.md @@ -1,13 +1,13 @@ # Reusable Select Component Usage -This file tracks every page/partial that uses ``, ``, or ``. +This file tracks every page/partial that uses ``, ``, ``, or ``. **Update this file whenever you add or remove a component from a view.** -When modifying any component or its data source (`app/Data/Countries.php`), check all pages in the relevant section below and verify the change works correctly in each context. +When modifying any component or its data source, check all pages in the relevant section below and verify the change works correctly in each context. --- -## Data source +## Data sources **`app/Data/Countries.php`** — `App\Data\Countries` @@ -20,18 +20,30 @@ When modifying any component or its data source (`app/Data/Countries.php`), chec Adding or renaming a field in `Countries::all()` requires updating the corresponding `for*()` method too. +**`app/Data/Languages.php`** — `App\Data\Languages` + +| Method | Used by component | +|---|---| +| `Languages::forLanguage()` | `` | +| `Languages::all()` | Via `forLanguage()` | + +Arabic and English are pinned to the top of the list; all others are sorted alphabetically by English name. Stored value is the ISO 639-1 code (e.g. `"ar"`, `"en"`). + --- ## Shared CSS / JS -The `.csd-*` CSS rules and the `window.CSD` class are duplicated across all three component files inside `@once` guards. If you change the look or behaviour of the dropdown, **update all three component files**: +The `.csd-*` CSS rules and the `window.CSD` class are duplicated across all four component files inside `@once` guards. If you change the look or behaviour of the dropdown, **update all four component files**: - `resources/views/components/phone-code-select.blade.php` - `resources/views/components/country-select.blade.php` - `resources/views/components/timezone-select.blade.php` +- `resources/views/components/language-select.blade.php` The `@once` Blade directive ensures the browser only receives one copy of the CSS/JS even when multiple components are on the same page. +`language-select` also emits an extra `@once('lsd-badge-styles')` block for the `.lsd-code` ISO badge that appears in place of a flag emoji. + --- ## `` @@ -88,7 +100,7 @@ The `@once` Blade directive ensures the browser only receives one copy of the CS | `resources/views/user/channel.blade.php` | `avatar` — circle 300×300 | Owner only; `update-url = profile.updateAvatar`; callback `onAvatarSaved` | | `resources/views/user/channel.blade.php` | `banner` — square 500×160 | Owner only; `update-url = profile.updateBanner`; callback `onBannerSaved` | | `resources/views/layouts/partials/upload-modal.blade.php` | `thumb_upload` — square 448×252 | Form mode; `target-input=thumbnail-modal`; output 1280px | -| `resources/views/layouts/partials/edit-video-modal.blade.php` | `thumb_edit` — square 448×252 | Form mode; `target-input=edit-thumbnail-input`; output 1280px | +| `resources/views/layouts/partials/edit-video-modal.blade.php` | `thumb_edit` — square 448×252 | Form mode; `target-input=edit-t1-thumbnail-input`; output 1280px | | `resources/views/videos/create.blade.php` | `thumb_create_mobile` — square 448×252 | Mobile; `target-input=thumbnail`; output 1280px | | `resources/views/videos/edit.blade.php` | `thumb_edit_mobile` — square 448×252 | Mobile; `target-input=edit-thumbnail`; output 1280px | | `resources/views/playlists/index.blade.php` | `thumb_pl_create` — square 448×252 | Form mode; `target-input=playlist-thumbnail-input`; output 1280px | @@ -140,6 +152,38 @@ Stored value: IANA timezone string (e.g. `"Asia/Bahrain"`). --- +## `` + +**File:** `resources/views/components/track-editor-form.blade.php` +**Props:** `prefix` (default `'t1'`), `isPrimary` (bool, default `false`), `languageName`, `languageId`, `titleName`, `titleId`, `descName`, `descId`, `videoFileInputId`. +**Behaviour:** Renders the full track editor form panel shown inside the Track Editor popup. Contains: optional Primary Track banner (when `:is-primary="true"`), language dropdown (``), title input, description textarea, video+thumbnail zone (hidden, shown for video/match type via `_editApplyMode`), and audio+slides zone (hidden, shown for music type). All element IDs are prefixed with `edit-{prefix}-*`. JS functions `editHandleThumbnail(input, prefix)`, `editRemoveThumbnail(event, prefix)`, `editSlidesZoneClick(event, tid)`, `editHandleSlides(files, tid)`, `editClearSlides(event, tid)` all accept the prefix/tid param. + +| View file | Prefix used | Notes | +|---|---|---| +| `resources/views/layouts/partials/edit-video-modal.blade.php` | `t1` | Primary track only; secondary tracks are built via JS (`_editAddExistingTrack`) | + +--- + +## `` + +**File:** `resources/views/components/language-select.blade.php` +**Data source:** `app/Data/Languages.php` — `Languages::forLanguage()` +**Stored value:** ISO 639-1 code (e.g. `"ar"`, `"en"`, `"fr"`). +**Props:** `name`, `id`, `value`, `label`, `placeholder`, `required`, `class`, `style`. +**Icon:** 2-letter uppercase ISO code rendered as a monospace badge (`.lsd-code`) — no flag emoji. +**Arabic and English are always pinned to the top** of the list; all other languages are alphabetical by English name. + +**Rule:** This component must be used for every language picker in the application. Never build a custom ``, inline list, or custom picker: | Need | Component | Stored value | |---|---|---| @@ -134,8 +134,9 @@ Structure: ` @@ -207,7 +208,7 @@ data-v="{{ $opt['value'] }}" data-s="{{ $opt['search'] }}" aria-selected="{{ $value === $opt['value'] ? 'true' : 'false' }}"> - {{ $opt['flag'] }} + {{ $opt['label'] }} @endforeach diff --git a/resources/views/components/language-select.blade.php b/resources/views/components/language-select.blade.php new file mode 100644 index 0000000..4addbee --- /dev/null +++ b/resources/views/components/language-select.blade.php @@ -0,0 +1,242 @@ +@props([ + 'name' => '', + 'id' => null, + 'value' => null, + 'label' => null, + 'placeholder' => 'Select language', + 'required' => false, + 'class' => '', + 'style' => '', +]) + +@php + use App\Data\Languages; + $options = Languages::forLanguage(); + $uid = 'lsd_' . ($id ?? $name) . '_' . substr(md5(uniqid()), 0, 8); + $inputId = $id ?? $name; + + $selFlag = null; + $selLabel = $placeholder; + if ($value) { + foreach ($options as $opt) { + if ($opt['value'] === $value) { + $selFlag = $opt['flag']; + $selLabel = $opt['label']; + break; + } + } + } + $isPlaceholder = !$value; +@endphp + +{{-- Shared dropdown styles (same .csd-* rules used by country/timezone/phone selects) --}} +@once('csd-styles') + +@endonce + +{{-- Language-specific: flag icon sizing --}} +@once('lsd-flag-styles') + +@endonce + +{{-- Shared dropdown JS (same window.CSD class used by country/timezone/phone selects) --}} +@once('csd-script') + +@endonce + +
+ + @if($label) + + @endif + + + + + + +
+ + diff --git a/resources/views/components/phone-code-select.blade.php b/resources/views/components/phone-code-select.blade.php index 229f328..697351f 100644 --- a/resources/views/components/phone-code-select.blade.php +++ b/resources/views/components/phone-code-select.blade.php @@ -15,7 +15,7 @@ $uid = 'csd_' . ($id ?? $name) . '_' . substr(md5(uniqid()), 0, 8); $inputId = $id ?? $name; - $selFlag = '🌐'; + $selFlag = ''; $selLabel = $placeholder; $selSub = ''; if ($value) { @@ -62,7 +62,8 @@ .csd-btn:focus-visible { border-color: var(--brand-red, #e61e1e); } .csd-btn[aria-expanded="true"] { border-color: var(--brand-red, #e61e1e); } -.csd-ico { font-size: 18px; line-height: 1; flex-shrink: 0; user-select: none; } +.csd-ico { font-size: 18px; line-height: 1; flex-shrink: 0; user-select: none; display: flex; align-items: center; } +.csd-ico .fi, .csd-opt-ico .fi { width: 22px; height: 16px; border-radius: 2px; display: inline-block; flex-shrink: 0; } .csd-val { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .csd-val.ph { color: var(--text-secondary, #aaa); } .csd-sub { font-size: 12px; color: var(--text-secondary, #aaa); white-space: nowrap; flex-shrink: 0; } @@ -71,7 +72,7 @@ .csd-panel { position: fixed; - z-index: 1060; + z-index: 1080; min-width: 220px; background: var(--bg-secondary, #1e1e1e); border: 1px solid var(--border-color, #303030); @@ -225,7 +226,7 @@ _pick(opt) { this.hidden.value = opt.dataset.v; - this.icoEl.textContent = opt.querySelector('.csd-opt-ico').textContent; + this.icoEl.innerHTML = opt.querySelector('.csd-opt-ico').innerHTML; this.valEl.textContent = opt.querySelector('.csd-opt-main').textContent; this.valEl.classList.remove('ph'); if (this.subEl) { @@ -258,7 +259,7 @@ aria-haspopup="listbox" aria-expanded="false" aria-label="{{ $label ?? 'Phone code' }}"> - {{ $selFlag }} + {{ $selLabel }} @if(!$isPlaceholder && $selSub) {{ $selSub }} @@ -279,7 +280,7 @@ data-v="{{ $opt['value'] }}" data-s="{{ $opt['search'] }}" aria-selected="{{ $value && str_starts_with($opt['value'], $value.'|') ? 'true' : 'false' }}"> - {{ $opt['flag'] }} + {{ $opt['label'] }} {{ $opt['secondary'] }} diff --git a/resources/views/components/playlist-card.blade.php b/resources/views/components/playlist-card.blade.php index 015eb92..a46b463 100644 --- a/resources/views/components/playlist-card.blade.php +++ b/resources/views/components/playlist-card.blade.php @@ -1,5 +1,13 @@ @props(['playlist']) +@php + $pl = $playlist; + $firstVid = $pl->videos->first(); + $plUrl = $firstVid + ? route('videos.show', $firstVid) . '?playlist=' . $pl->share_token + : route('playlists.show', $pl->id); +@endphp + @once @endonce -@php $pl = $playlist; @endphp -
- +
{{ $pl->name }}
@@ -94,7 +100,7 @@

- {{ $pl->name }} + {{ $pl->name }}

@if($pl->user)
  • Download MP3 @@ -292,7 +292,7 @@ Download Video @endif - Download MP3 diff --git a/resources/views/components/video-card.blade.php b/resources/views/components/video-card.blade.php index 4271d0c..4d05b96 100644 --- a/resources/views/components/video-card.blade.php +++ b/resources/views/components/video-card.blade.php @@ -1,6 +1,8 @@ @props(['video' => null, 'size' => 'medium']) @php +use App\Data\Languages; + $videoUrl = $video ? asset('storage/videos/' . $video->filename) : null; $thumbnailUrl = $video && $video->thumbnail ? route('media.thumbnail', $video->thumbnail) @@ -18,6 +20,9 @@ $isShorts = $video && $video->isShorts(); // Check if current user is the owner of the video $isOwner = $video && auth()->check() && auth()->id() == $video->user_id; +// Language flag code (null when no language set) +$langFlag = $video ? Languages::flag($video->language) : null; + // Size classes $sizeClasses = match($size) { 'small' => 'yt-video-card-sm', @@ -70,6 +75,9 @@ $sizeClasses = match($size) {

    + @if($langFlag) + + @endif {{ $video->title ?? 'Untitled Video' }}

    @@ -481,6 +489,19 @@ $sizeClasses = match($size) { .yt-video-card .yt-video-title a { color: inherit; text-decoration: none; + display: flex; + align-items: baseline; + gap: 5px; +} +.vc-lang-flag { + display: inline-block; + width: 16px; + height: 12px; + border-radius: 2px; + flex-shrink: 0; + vertical-align: baseline; + position: relative; + top: 1px; } .yt-video-card .yt-channel-icon { diff --git a/resources/views/components/video-insights.blade.php b/resources/views/components/video-insights.blade.php index 793cb58..67eb5b8 100644 --- a/resources/views/components/video-insights.blade.php +++ b/resources/views/components/video-insights.blade.php @@ -10,8 +10,9 @@ @if($isVideoOwner) {{-- Panel: placed inside .vdb-wrap right after the About panel --}} -
    +
    @@ -185,11 +187,12 @@
    - - - diff --git a/resources/views/layouts/partials/edit-video-modal.blade.php b/resources/views/layouts/partials/edit-video-modal.blade.php index 0dd5416..48ac078 100644 --- a/resources/views/layouts/partials/edit-video-modal.blade.php +++ b/resources/views/layouts/partials/edit-video-modal.blade.php @@ -1,1513 +1,1023 @@ - -
  • + + ${o.label} + ${o.native} +
  • ` + ).join(''); + } + + function ltToggleCreate(trackId) { + const item = document.getElementById('ltac-' + trackId); + const arr = document.getElementById('ltac-' + trackId + '-arr'); + if (!item) return; + const isOpen = item.classList.contains('open'); + if (isOpen) { + item.classList.remove('open'); + if (arr) { arr.style.transform = ''; arr.style.color = ''; } + } else { + item.classList.add('open'); + if (arr) { arr.style.transform = 'rotate(180deg)'; arr.style.color = 'var(--brand-red)'; } + } + } + + function updateLtacHeader(trackId, langCode) { + const lang = LANG_OPTIONS_CREATE.find(o => o.value === langCode); + if (!lang) return; + const flagEl = document.getElementById('ltac-' + trackId + '-flag'); + const nameEl = document.getElementById('ltac-' + trackId + '-name'); + if (flagEl) flagEl.innerHTML = ``; + if (nameEl) { + const prefix = trackId === 't1' ? 'Primary' : 'Track ' + (parseInt(trackId.replace('e', '')) + 1); + nameEl.textContent = prefix + ' — ' + lang.label; + } + } + + function ltRemoveCreate(e, trackId) { + e.preventDefault(); + e.stopPropagation(); + const item = document.getElementById('ltac-' + trackId); + if (item) item.remove(); + delete _cSlidesData['c' + trackId]; + } + + function addLangTrackCreate() { + const n = ++_ltCreateExtraCounter; + const trackNum = n + 1; + const uid = 'ltce_' + n; + + const item = document.createElement('div'); + item.className = 'ltac-item open'; + item.id = 'ltac-e' + n; + + item.innerHTML = ` +
    +
    + ${trackNum} + + + + Track ${trackNum} — Select language +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + +
    + + Click or drag to add cover images +
    + +
    +
    +
    + +
    + + + +
    +
    +
    `; + + document.getElementById('ltac-extra').appendChild(item); + _cSlidesData['ce' + n] = []; + + if (window.CSD) new CSD(uid); + + const hiddenInput = document.getElementById('csd_v_' + uid); + if (hiddenInput) { + hiddenInput.dataset.trackId = 'e' + n; + hiddenInput.addEventListener('change', function() { + updateLtacHeader('e' + n, this.value); + }); + } + } + + // Wire primary language select → update track 1 header + ;(function() { + const plInput = document.getElementById('primary_language_create'); + if (plInput) { + plInput.dataset.trackId = 't1'; + plInput.addEventListener('change', function() { + updateLtacHeader('t1', this.value); + }); + } + + // Delegated fallback: catches any language hidden-input change within the tracks section + const section = document.getElementById('lang-tracks-section-create'); + if (section) { + section.addEventListener('change', function(e) { + const tid = e.target.dataset && e.target.dataset.trackId; + if (tid && e.target.value) updateLtacHeader(tid, e.target.value); + }); + } + }());
    + + {{-- Primary Language (audio only) --}} +
    + +
    + + {{-- Language Tracks Manager (audio only) --}} +
    + + + {{-- Existing tracks --}} +
    + @forelse($video->audioTracks as $track) +
    + + + + +
    + @empty +

    No extra language tracks yet.

    + @endforelse +
    + + {{-- New tracks to add --}} +
    + + +
    @endif {{-- Video Type --}} @@ -372,6 +429,8 @@ if (e.target.closest('.btn-remove')) return; if (typeof window.openCropperModal_thumb_edit_mobile === 'function') { window.openCropperModal_thumb_edit_mobile(); + const internal = document.getElementById('tcInput_thumb_edit_mobile'); + if (internal) internal.click(); } else { editThumbInput.click(); } @@ -536,6 +595,9 @@ @if($video->isAudioOnly()) // Append newly added slide files epSlidesData.filter(s => s.file).forEach(s => formData.append('slides_add[]', s.file, s.file.name)); + + // Append new extra track files (already have name="extra_track_files[]" in DOM) + // delete_track_ids are already wired via enabled hidden inputs @endif fetch('{{ route("videos.update", $video) }}', { @@ -561,6 +623,80 @@ .catch(() => showEditError('Something went wrong. Please try again.')); }); + @if($video->isAudioOnly()) + // ── Language track management ───────────────────────────────────────────── + const EP_LANG_OPTIONS = @json(\App\Data\Languages::forLanguage()); + let _epTrackCounter = 0; + + function _epMarkDelTrack(trackId, btn) { + const hiddenInput = document.getElementById('ep-del-' + trackId); + if (hiddenInput) { + hiddenInput.value = trackId; + hiddenInput.disabled = false; + } + btn.closest('[data-track-id]').remove(); + const existing = document.getElementById('ep-tracks-existing'); + if (existing && existing.querySelectorAll('[data-track-id]').length === 0) { + let msg = document.getElementById('ep-tracks-empty-msg'); + if (!msg) { + msg = document.createElement('p'); + msg.id = 'ep-tracks-empty-msg'; + msg.style.cssText = 'font-size:12px;color:#888;margin:0 0 4px;'; + msg.textContent = 'No extra language tracks yet.'; + existing.appendChild(msg); + } + } + } + + function buildLangSelectHtmlEp(uid) { + const opts = EP_LANG_OPTIONS.map(o => + `
  • + + ${o.label} + ${o.native} +
  • ` + ).join(''); + return `
    + + + +
    `; + } + + function addEpTrackRow(e) { + if (e) e.preventDefault(); + const uid = 'epet_' + (++_epTrackCounter); + const row = document.createElement('div'); + row.style.cssText = 'display:flex;flex-direction:column;gap:8px;background:#151515;border:1px solid #333;border-radius:8px;padding:10px 12px;'; + row.innerHTML = ` +
    +
    ${buildLangSelectHtmlEp(uid)}
    + +
    + + `; + document.getElementById('ep-tracks-new-list').appendChild(row); + if (window.CSD) new CSD('csd_' + uid); + } + @endif + function showEditError(message) { const status = document.getElementById('edit-status'); status.innerHTML = ' ' + message; diff --git a/resources/views/videos/partials/audio-player.blade.php b/resources/views/videos/partials/audio-player.blade.php index 0832216..f520d2d 100644 --- a/resources/views/videos/partials/audio-player.blade.php +++ b/resources/views/videos/partials/audio-player.blade.php @@ -1,11 +1,35 @@ @php - $audioUrl = route('videos.stream', $video); + $audioUrl = route('videos.stream', $video) . '?v=' . $video->updated_at->timestamp; $coverUrl = $video->thumbnail ? route('media.thumbnail', $video->thumbnail) : asset('storage/images/logo.png'); $nextUrl = isset($nextVideo, $playlist) ? route('videos.show', $nextVideo).'?playlist='.$playlist->share_token : null; $prevUrl = isset($previousVideo, $playlist) ? route('videos.show', $previousVideo).'?playlist='.$playlist->share_token : null; $slideUrls = $video->slides->count() > 1 ? $video->slides->map(fn($s) => route('media.thumbnail', $s->filename))->values()->all() : []; + // Build all-tracks list: primary first, then extra language tracks (skip extras that duplicate primary language) + $primaryLang = $video->language ?? 'default'; + $allLangData = \App\Data\Languages::all(); + $primaryFlag = $allLangData[$primaryLang]['flag'] ?? null; + $allAudioTracks = collect([[ + 'id' => 0, + 'language' => $primaryLang, + 'label' => $video->language ? strtoupper($video->language) : 'Default', + 'flag' => $primaryFlag, + 'stream_url' => $audioUrl, + 'title' => $video->title, + 'description' => $video->description ?? '', + 'dl_url' => route('videos.downloadMp3', $video), + ]])->concat($video->audioTracks->map(fn($t) => [ + 'id' => $t->id, + 'language' => $t->language, + 'label' => $t->label, + 'flag' => $allLangData[$t->language]['flag'] ?? null, + 'stream_url' => route('videos.audio-track', ['video' => $video, 'track' => $t->id]) . '?v=' . $t->updated_at->timestamp, + 'title' => $t->title ?? '', + 'description' => $t->description ?? '', + 'dl_url' => route('videos.audio-track', ['video' => $video, 'track' => $t->id]) . '?download=1&v=' . $t->updated_at->timestamp, + ])); + $hasMultipleTracks = $allAudioTracks->count() > 1; @endphp
    @@ -83,6 +107,34 @@
    + {{-- Language flag — always in DOM; hidden only when no language/flag is set --}} + +
    @endforeach
    - {{-- Loop toggle --}} -
    - - Loop - Off -
    -
    + {{-- Loop — standalone button, outside gear --}} + + {{-- Bars visualiser toggle --}}