Propresentor
This commit is contained in:
parent
ed89de631b
commit
f8ffd3a7fa
37
Chruch.Net/Chruch.Net.sln
Normal file
37
Chruch.Net/Chruch.Net.sln
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.4.33205.214
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chruch.Net", "Chruch.Net.csproj", "{755399EA-BCFB-48A7-8A85-923054F979CA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "..\TestProject1\TestProject1.csproj", "{F75C4820-424C-4AA9-8C0E-B6527AB76C2D}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropresentorScript", "..\PropresentorScript\PropresentorScript.csproj", "{D9C65F50-0828-4E88-9C8F-250A0AB57C98}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{755399EA-BCFB-48A7-8A85-923054F979CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{755399EA-BCFB-48A7-8A85-923054F979CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{755399EA-BCFB-48A7-8A85-923054F979CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{755399EA-BCFB-48A7-8A85-923054F979CA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F75C4820-424C-4AA9-8C0E-B6527AB76C2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F75C4820-424C-4AA9-8C0E-B6527AB76C2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F75C4820-424C-4AA9-8C0E-B6527AB76C2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F75C4820-424C-4AA9-8C0E-B6527AB76C2D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {CA8E6F4D-EBB4-4298-B882-FDB841B667BE}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
28
Church.Net.DAL.EFCoreDBF/D2MobInfoDAL.cs
Normal file
28
Church.Net.DAL.EFCoreDBF/D2MobInfoDAL.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Church.Net.DAL.EF;
|
||||||
|
using Church.Net.DAL.EFCoreDBF.Core;
|
||||||
|
using Church.Net.DAL.EFCoreDBF.Interface;
|
||||||
|
using Church.Net.Entity.Games.MD2;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Church.Net.DAL.EFCoreDBF
|
||||||
|
{
|
||||||
|
public class D2MobInfoDAL : CrudDALCBase<MobInfo>, ICrudDAL<MobInfo>
|
||||||
|
{
|
||||||
|
public D2MobInfoDAL(DatabaseOptions databaseOptions) : base(databaseOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override IQueryable<MobInfo> InitQuery(ChurchNetContext dbContext)
|
||||||
|
{
|
||||||
|
return dbContext.Md2MobInfos
|
||||||
|
.Include(m=>m.MobLevelInfos).ThenInclude(s=>s.AttackInfo)
|
||||||
|
.Include(m => m.MobLevelInfos).ThenInclude(s => s.DefenceInfo)
|
||||||
|
.Include(m => m.MobLevelInfos).ThenInclude(s => s.Skills)
|
||||||
|
.Include(m => m.Skills);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,10 +32,14 @@ namespace Church.Net.Entity.Games.MD2
|
|||||||
|
|
||||||
public enum MobSkillTarget
|
public enum MobSkillTarget
|
||||||
{
|
{
|
||||||
LeastHp,
|
Random = 40,
|
||||||
LeastMaxHp,
|
LeastHp = 50,
|
||||||
LeastMana,
|
LeastMp = 60,
|
||||||
LeastMaxMana,
|
HighestHp = 70,
|
||||||
|
HighestMp = 80,
|
||||||
|
LowestLevel = 90,
|
||||||
|
MostCorruption = 200,
|
||||||
|
LeastCorruption = 201
|
||||||
}
|
}
|
||||||
public enum MD2Icon
|
public enum MD2Icon
|
||||||
{
|
{
|
||||||
@ -129,6 +133,8 @@ namespace Church.Net.Entity.Games.MD2
|
|||||||
public virtual MobLevelInfo MobLevelInfo { get; set; }
|
public virtual MobLevelInfo MobLevelInfo { get; set; }
|
||||||
|
|
||||||
public MobSkillType Type { get; set; }
|
public MobSkillType Type { get; set; }
|
||||||
|
public MobSkillTarget? SkillTarget { get; set; }
|
||||||
|
|
||||||
public int ClawRoll { get; set; }
|
public int ClawRoll { get; set; }
|
||||||
public int SkillRoll { get; set; }
|
public int SkillRoll { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|||||||
58
Church.Net.WebAPI/Bindings/APIBinding.cs
Normal file
58
Church.Net.WebAPI/Bindings/APIBinding.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using Church.Net.DAL.EF;
|
||||||
|
using Church.Net.DAL.EFCoreDBF.Core;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using WebAPI.Hubs;
|
||||||
|
using WebAPI;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
using WebAPI.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using WebAPI.Handlers;
|
||||||
|
|
||||||
|
namespace Church.Net.WebAPI.Bindings
|
||||||
|
{
|
||||||
|
public class APIBinding : IBinding
|
||||||
|
{
|
||||||
|
public void Binding(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddControllers().AddNewtonsoftJson(options =>
|
||||||
|
{
|
||||||
|
// Use the default property (Pascal) casing
|
||||||
|
options.SerializerSettings.ContractResolver = new DefaultContractResolver()
|
||||||
|
{
|
||||||
|
NamingStrategy = new CamelCaseNamingStrategy(),
|
||||||
|
|
||||||
|
};
|
||||||
|
//options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Unspecified;
|
||||||
|
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
|
||||||
|
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSignalR();
|
||||||
|
|
||||||
|
services.AddSingleton<GameRoomLogic>();
|
||||||
|
|
||||||
|
services.AddSingleton<GameRoomHub>();
|
||||||
|
|
||||||
|
//Localted at \\ArkNAS\docker\ChurchAPI\docker-compose.yaml
|
||||||
|
string databaseConnString = Environment.GetEnvironmentVariable("DB_CONN_STRING");
|
||||||
|
#if DEBUG
|
||||||
|
databaseConnString = "Host=192.168.68.55;Port=49154;Database=Church;Username=chris;Password=1124";
|
||||||
|
#endif
|
||||||
|
//services.AddSingleton(_ => new DatabaseOptions { ConnectionString = databaseConnString });
|
||||||
|
services.AddSingleton(_ => new DatabaseOptions { ConnectionString = databaseConnString });
|
||||||
|
//services.AddSingleton<ChurchNetContext>(new ChurchNetContext());
|
||||||
|
services.AddDbContext<ChurchNetContext>(options =>
|
||||||
|
options.UseNpgsql(
|
||||||
|
//Configuration.GetConnectionString()
|
||||||
|
//"Host=192.168.68.55;Port=49154;Database=ChurchSandbox;Username=chris;Password=1124"
|
||||||
|
databaseConnString
|
||||||
|
));
|
||||||
|
|
||||||
|
services.AddHostedService<WorkerService>();
|
||||||
|
services.AddSingleton<IAuthorizationMiddlewareResultHandler, BasicAuthorizationMiddlewareResultHandler>();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Church.Net.WebAPI/Bindings/DALBinding.cs
Normal file
25
Church.Net.WebAPI/Bindings/DALBinding.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Church.Net.DAL.EFCoreDBF;
|
||||||
|
using Church.Net.DAL.EFCoreDBF.Core;
|
||||||
|
using Church.Net.DAL.EFCoreDBF.Interface;
|
||||||
|
using Church.Net.Entity;
|
||||||
|
using Church.Net.Entity.Games.MD2;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using WebAPI.Logics.Interface;
|
||||||
|
using WebAPI.Logics;
|
||||||
|
|
||||||
|
namespace Church.Net.WebAPI.Bindings
|
||||||
|
{
|
||||||
|
public class DALBinding : IBinding
|
||||||
|
{
|
||||||
|
public void Binding(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped(typeof(ICrudDAL<>), typeof(CrudDALCBase<>));
|
||||||
|
services.AddScoped(typeof(ICombinedKeyCrudDAL<>), typeof(CombinedKeyCrudDALCBase<>));
|
||||||
|
|
||||||
|
services.AddScoped<ICrudDAL<MobInfo>, D2MobInfoDAL>();
|
||||||
|
//D2MobInfoDAL: CrudDALCBase<MobInfo>, ICrudDAL<MobInfo>
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Church.Net.WebAPI/Bindings/IBinding.cs
Normal file
9
Church.Net.WebAPI/Bindings/IBinding.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Church.Net.WebAPI.Bindings
|
||||||
|
{
|
||||||
|
public interface IBinding
|
||||||
|
{
|
||||||
|
void Binding(IServiceCollection services);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Church.Net.WebAPI/Bindings/LogicBinding.cs
Normal file
40
Church.Net.WebAPI/Bindings/LogicBinding.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using WebAPI.Logics.Core;
|
||||||
|
using WebAPI.Logics.Interface;
|
||||||
|
using WebAPI.Logics;
|
||||||
|
using WebAPI.Services.AutoReplyCommands;
|
||||||
|
using WebAPI.Services.Interfaces;
|
||||||
|
using WebAPI.Services;
|
||||||
|
using Church.Net.Entity;
|
||||||
|
|
||||||
|
namespace Church.Net.WebAPI.Bindings
|
||||||
|
{
|
||||||
|
public class LogicBinding : IBinding
|
||||||
|
{
|
||||||
|
public void Binding(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<LineAutoBotService>();
|
||||||
|
services.AddScoped<IAutoReplyCommand, ArChurchInfo>();
|
||||||
|
services.AddScoped<IAutoReplyCommand, ArArkCellGroupInfo>();
|
||||||
|
services.AddScoped<IAutoReplyCommand, ArArkCellGroupDinner>();
|
||||||
|
services.AddScoped<IAutoReplyCommand, ArArkCellGroupPrayer>();
|
||||||
|
services.AddScoped<IAutoReplyCommand, ArHappinessGroupTask>();
|
||||||
|
services.AddScoped<IAutoReplyCommand, ArHappinessBEST>();
|
||||||
|
|
||||||
|
//services.AddScoped<IScheduledTask, MorningPrayer>();
|
||||||
|
|
||||||
|
|
||||||
|
services.AddScoped<VideoDownloadLogic>();
|
||||||
|
services.AddScoped<LogicService>();
|
||||||
|
services.AddScoped<PastoralDomainLogic>();
|
||||||
|
services.AddScoped(typeof(ICrudLogic<>), typeof(LogicBase<>));
|
||||||
|
services.AddScoped(typeof(ICombinedKeyCrudLogic<>), typeof(CombinedKeyLogicBase<>));
|
||||||
|
|
||||||
|
services.AddScoped<LineMessagingAccountLogic>();
|
||||||
|
services.AddScoped<ILoggingService, DbLoggingService>();
|
||||||
|
services.AddScoped<IdentityService>();
|
||||||
|
services.AddScoped<ICrudLogic<FamilyMember>, MemberLogic>();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,4 +16,22 @@ namespace WebAPI.Controllers
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Route("[controller]/[action]")]
|
||||||
|
[ApiController]
|
||||||
|
public class MobLevelInfoController : ApiControllerBase<MobLevelInfo>
|
||||||
|
{
|
||||||
|
public MobLevelInfoController(ICrudLogic<MobLevelInfo> logic) : base(logic)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[Route("[controller]/[action]")]
|
||||||
|
[ApiController]
|
||||||
|
public class MobSkillController : ApiControllerBase<MobSkill>
|
||||||
|
{
|
||||||
|
public MobSkillController(ICrudLogic<MobSkill> logic) : base(logic)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
Church.Net.WebAPI/Extensions/ServiceCollectionExtensions.cs
Normal file
98
Church.Net.WebAPI/Extensions/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Church.Net.WebAPI.Extensions
|
||||||
|
{
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection Remove<T>(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
if (services.IsReadOnly)
|
||||||
|
{
|
||||||
|
throw new ReadOnlyException($"{nameof(services)} is read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceDescriptors = services.Where(descriptor => descriptor.ServiceType == typeof(T));
|
||||||
|
if (serviceDescriptors.Any())
|
||||||
|
{
|
||||||
|
foreach (var service in serviceDescriptors)
|
||||||
|
{
|
||||||
|
services.Remove(service);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection OverWriteTransient<TService, TImplementation>(this IServiceCollection services) where TService : class where TImplementation : class, TService
|
||||||
|
{
|
||||||
|
services.Remove<TService>();
|
||||||
|
services.AddTransient<TService, TImplementation>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection OverWriteScoped<TService, TImplementation>(this IServiceCollection services) where TService : class where TImplementation : class, TService
|
||||||
|
{
|
||||||
|
services.Remove<TService>();
|
||||||
|
services.AddScoped<TService, TImplementation>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
public static IServiceCollection OverWriteSingleton<TService, TImplementation>(this IServiceCollection services) where TService : class where TImplementation : class, TService
|
||||||
|
{
|
||||||
|
services.Remove<TService>();
|
||||||
|
services.AddSingleton<TService, TImplementation>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public static void AddFactory<TService, TImplementation>(this IServiceCollection services)
|
||||||
|
//where TService : class
|
||||||
|
//where TImplementation : class, TService
|
||||||
|
//{
|
||||||
|
// services.AddTransient<TService, TImplementation>();
|
||||||
|
// services.AddSingleton<Func<TService>>(x => () => x.GetService<TService>());
|
||||||
|
// services.AddSingleton<IFactory<TService>, Factory<TService>>();
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
public static IServiceCollection AddMultiScoped<IService, TBaseImplementation>(this IServiceCollection services) where IService : class where TBaseImplementation : class, IService
|
||||||
|
{
|
||||||
|
foreach (var type in GetImplementedTypes<IService, TBaseImplementation>())
|
||||||
|
{
|
||||||
|
services.AddScoped(h => (IService)Activator.CreateInstance(type));
|
||||||
|
}
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
public static IServiceCollection AddMultiTransient<IService, TBaseImplementation>(this IServiceCollection services) where IService : class where TBaseImplementation : class, IService
|
||||||
|
{
|
||||||
|
foreach (var type in GetImplementedTypes<IService, TBaseImplementation>())
|
||||||
|
{
|
||||||
|
services.AddTransient(h => (IService)Activator.CreateInstance(type));
|
||||||
|
}
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
public static IServiceCollection AddMultiSingleton<IService, TBaseImplementation>(this IServiceCollection services) where IService : class where TBaseImplementation : class, IService
|
||||||
|
{
|
||||||
|
foreach (var type in GetImplementedTypes<IService, TBaseImplementation>())
|
||||||
|
{
|
||||||
|
services.AddSingleton(h => (IService)Activator.CreateInstance(type));
|
||||||
|
}
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Type> GetImplementedTypes<TService, TBaseImplementation>() where TBaseImplementation : class, TService
|
||||||
|
{
|
||||||
|
|
||||||
|
Type interfaceType = typeof(TService);
|
||||||
|
Type baseType = typeof(TBaseImplementation);
|
||||||
|
IEnumerable<Type> types = System.Reflection.Assembly.GetAssembly(baseType).GetTypes()
|
||||||
|
.Where(p => !p.IsInterface && p != baseType && interfaceType.IsAssignableFrom(p));
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ using Church.Net.DAL.EF;
|
|||||||
using Church.Net.DAL.EFCoreDBF.Core;
|
using Church.Net.DAL.EFCoreDBF.Core;
|
||||||
using Church.Net.DAL.EFCoreDBF.Interface;
|
using Church.Net.DAL.EFCoreDBF.Interface;
|
||||||
using Church.Net.Entity;
|
using Church.Net.Entity;
|
||||||
|
using Church.Net.Entity.Games.MD2;
|
||||||
|
using Church.Net.WebAPI.Bindings;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
@ -17,6 +19,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
@ -48,73 +51,12 @@ namespace WebAPI
|
|||||||
|
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
services.AddControllers().AddNewtonsoftJson(options =>
|
|
||||||
{
|
|
||||||
// Use the default property (Pascal) casing
|
|
||||||
options.SerializerSettings.ContractResolver = new DefaultContractResolver()
|
|
||||||
{
|
|
||||||
NamingStrategy = new CamelCaseNamingStrategy(),
|
|
||||||
|
|
||||||
};
|
|
||||||
//options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Unspecified;
|
|
||||||
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
|
|
||||||
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
services.AddSignalR();
|
new APIBinding().Binding(services);
|
||||||
|
new LogicBinding().Binding(services);
|
||||||
|
new DALBinding().Binding(services);
|
||||||
|
|
||||||
services.AddSingleton<GameRoomLogic>();
|
|
||||||
|
|
||||||
services.AddSingleton<GameRoomHub>();
|
|
||||||
|
|
||||||
//Localted at \\ArkNAS\docker\ChurchAPI\docker-compose.yaml
|
|
||||||
string databaseConnString = Environment.GetEnvironmentVariable("DB_CONN_STRING");
|
|
||||||
#if DEBUG
|
|
||||||
databaseConnString = "Host=192.168.68.55;Port=49154;Database=Church;Username=chris;Password=1124";
|
|
||||||
#endif
|
|
||||||
//services.AddSingleton(_ => new DatabaseOptions { ConnectionString = databaseConnString });
|
|
||||||
services.AddSingleton(_ => new DatabaseOptions { ConnectionString = databaseConnString });
|
|
||||||
//services.AddSingleton<ChurchNetContext>(new ChurchNetContext());
|
|
||||||
services.AddDbContext<ChurchNetContext>(options =>
|
|
||||||
options.UseNpgsql(
|
|
||||||
//Configuration.GetConnectionString()
|
|
||||||
//"Host=192.168.68.55;Port=49154;Database=ChurchSandbox;Username=chris;Password=1124"
|
|
||||||
databaseConnString
|
|
||||||
));
|
|
||||||
|
|
||||||
services.AddScoped<LineAutoBotService>();
|
|
||||||
services.AddScoped<IAutoReplyCommand, ArChurchInfo>();
|
|
||||||
services.AddScoped<IAutoReplyCommand, ArArkCellGroupInfo>();
|
|
||||||
services.AddScoped<IAutoReplyCommand, ArArkCellGroupDinner>();
|
|
||||||
services.AddScoped<IAutoReplyCommand, ArArkCellGroupPrayer>();
|
|
||||||
services.AddScoped<IAutoReplyCommand, ArHappinessGroupTask>();
|
|
||||||
services.AddScoped<IAutoReplyCommand, ArHappinessBEST>();
|
|
||||||
|
|
||||||
//services.AddScoped<IScheduledTask, MorningPrayer>();
|
|
||||||
|
|
||||||
|
|
||||||
services.AddScoped<VideoDownloadLogic>();
|
|
||||||
services.AddScoped<LogicService>();
|
|
||||||
services.AddScoped<PastoralDomainLogic>();
|
|
||||||
services.AddScoped(typeof(ICrudLogic<>), typeof(LogicBase<>));
|
|
||||||
services.AddScoped(typeof(ICrudDAL<>), typeof(CrudDALCBase<>));
|
|
||||||
services.AddScoped(typeof(ICombinedKeyCrudLogic<>), typeof(CombinedKeyLogicBase<>));
|
|
||||||
services.AddScoped(typeof(ICombinedKeyCrudDAL<>), typeof(CombinedKeyCrudDALCBase<>));
|
|
||||||
services.AddScoped<LineMessagingAccountLogic>();
|
|
||||||
services.AddScoped<ILoggingService, DbLoggingService>();
|
|
||||||
services.AddScoped<IdentityService>();
|
|
||||||
services.AddScoped<ICrudLogic<FamilyMember>, MemberLogic>();
|
|
||||||
services.AddHostedService<WorkerService>();
|
|
||||||
//services.AddMvc(o=>o.Filters.Add(typeof(HandleExceptionFilter)));
|
|
||||||
//services.AddMvc(o => o.Filters.Add(new HandleExceptionFilter(services.BuildServiceProvider().GetService<ILoggingService>())));
|
|
||||||
//services.BuildServiceProvider().GetService<ILoggingService>();
|
|
||||||
services.AddSingleton<IAuthorizationMiddlewareResultHandler, BasicAuthorizationMiddlewareResultHandler>();
|
|
||||||
|
|
||||||
//ObjectFactory.Initialize(x =>
|
|
||||||
// x.For<HttpContextBase>()
|
|
||||||
// .HybridHttpOrThreadLocalScoped()
|
|
||||||
// .Use(() => new HttpContextWrapper(HttpContext.Current));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
|||||||
@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject", "TestProject\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LineMessaging", "LineMessaging\LineMessaging.csproj", "{EF90BF58-C73D-408D-8ECE-F45425ADB81E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LineMessaging", "LineMessaging\LineMessaging.csproj", "{EF90BF58-C73D-408D-8ECE-F45425ADB81E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropresentorScript", "PropresentorScript\PropresentorScript.csproj", "{D9C65F50-0828-4E88-9C8F-250A0AB57C98}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -51,6 +53,10 @@ Global
|
|||||||
{EF90BF58-C73D-408D-8ECE-F45425ADB81E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EF90BF58-C73D-408D-8ECE-F45425ADB81E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{EF90BF58-C73D-408D-8ECE-F45425ADB81E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EF90BF58-C73D-408D-8ECE-F45425ADB81E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{EF90BF58-C73D-408D-8ECE-F45425ADB81E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{EF90BF58-C73D-408D-8ECE-F45425ADB81E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D9C65F50-0828-4E88-9C8F-250A0AB57C98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
6
PropresentorScript/App.config
Normal file
6
PropresentorScript/App.config
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
647
PropresentorScript/Program.cs
Normal file
647
PropresentorScript/Program.cs
Normal file
@ -0,0 +1,647 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
|
||||||
|
namespace PropresentorScript
|
||||||
|
{
|
||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// 用法:dotnet run "path/to/song.txt"
|
||||||
|
string inputText;
|
||||||
|
if (args.Length > 0 && File.Exists(args[0]))
|
||||||
|
{
|
||||||
|
inputText = File.ReadAllText(args[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 沒給檔案就用內建範例(可自行刪除)
|
||||||
|
inputText = @"向我神
|
||||||
|
To Our God
|
||||||
|
|
||||||
|
[Verse 1]
|
||||||
|
從塵土灰燼 祢愛贖回我
|
||||||
|
Up from the ashes Your love has brought us
|
||||||
|
脫離黑暗中 進入光明
|
||||||
|
Out of the darkness into the light;
|
||||||
|
挪去我憂傷
|
||||||
|
Lifting our sorrows
|
||||||
|
承受我重擔 醫治我心
|
||||||
|
Bearing our burdens healing our hearts;
|
||||||
|
|
||||||
|
|
||||||
|
[Chorus]
|
||||||
|
向我神 我揚聲歌唱
|
||||||
|
To our God we lift up one voice
|
||||||
|
向我神 我高舉雙手
|
||||||
|
To our God we lift up one song;
|
||||||
|
向我神 同心來宣揚
|
||||||
|
To our God we lift up one voice
|
||||||
|
高唱 哈利路亞
|
||||||
|
Singing Hallelujah;";
|
||||||
|
}
|
||||||
|
|
||||||
|
Song song = ParseSong(inputText);
|
||||||
|
|
||||||
|
XDocument pro6 = BuildPro6(song, 1920, 1080);
|
||||||
|
string safeTitle = MakeSafeFileName(song.TitleZh ?? song.Title);
|
||||||
|
string outputPath = Path.Combine(Directory.GetCurrentDirectory(), safeTitle + ".pro6");
|
||||||
|
pro6.Save(outputPath);
|
||||||
|
Console.WriteLine("Wrote: " + outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- Models ----------------
|
||||||
|
|
||||||
|
class Section
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
// 每個元素為一張「原始」投影片的多行文字(以 \n 分隔);後續會再自動分頁
|
||||||
|
public List<string> Slides { get; set; } = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Song
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = "Untitled"; // 後備
|
||||||
|
public string? TitleZh { get; set; } // 中文歌名(若有)
|
||||||
|
public string? TitleEn { get; set; } // 英文歌名(若有)
|
||||||
|
public List<Section> Sections { get; set; } = new List<Section>();
|
||||||
|
|
||||||
|
public string FooterText(string sectionName)
|
||||||
|
{
|
||||||
|
string baseTitle = !string.IsNullOrWhiteSpace(TitleEn) && !string.IsNullOrWhiteSpace(TitleZh)
|
||||||
|
? (TitleZh + " / " + TitleEn)
|
||||||
|
: (TitleZh ?? Title);
|
||||||
|
return baseTitle + " - " + sectionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- Parsing ----------------
|
||||||
|
|
||||||
|
// 規則:
|
||||||
|
// - 第一個非空行 = 標題1(可能是中或英)
|
||||||
|
// - 第二個非空行若不是 [Header],則當作標題2
|
||||||
|
// - [xxx] 開頭代表段落標頭;其後文字累積,遇到 ; 或 ; 就分成新投影片
|
||||||
|
static Song ParseSong(string raw)
|
||||||
|
{
|
||||||
|
string[] lines = raw.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
// 標題1
|
||||||
|
while (i < lines.Length && string.IsNullOrWhiteSpace(lines[i])) i++;
|
||||||
|
string title1 = (i < lines.Length) ? lines[i].Trim() : "Untitled";
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// 標題2(若下一個非空行不是 header)
|
||||||
|
while (i < lines.Length && string.IsNullOrWhiteSpace(lines[i])) i++;
|
||||||
|
string? title2 = null;
|
||||||
|
if (i < lines.Length && !IsHeader(lines[i].Trim(), out _))
|
||||||
|
{
|
||||||
|
title2 = lines[i].Trim();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Song song = new Song();
|
||||||
|
if (ContainsCjk(title1))
|
||||||
|
{
|
||||||
|
song.TitleZh = title1;
|
||||||
|
song.Title = title1;
|
||||||
|
if (!string.IsNullOrWhiteSpace(title2)) song.TitleEn = title2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
song.Title = title1;
|
||||||
|
if (!string.IsNullOrWhiteSpace(title2))
|
||||||
|
{
|
||||||
|
if (ContainsCjk(title2)) song.TitleZh = title2;
|
||||||
|
else song.TitleEn = title2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Section> sections = new List<Section>();
|
||||||
|
Section? current = null;
|
||||||
|
List<string> slideBuffer = new List<string>();
|
||||||
|
|
||||||
|
void FlushSlide()
|
||||||
|
{
|
||||||
|
if (current == null) return;
|
||||||
|
string joined = string.Join("\n", slideBuffer).Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(joined))
|
||||||
|
current.Slides.Add(joined);
|
||||||
|
slideBuffer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushSection()
|
||||||
|
{
|
||||||
|
if (current == null) return;
|
||||||
|
if (slideBuffer.Count > 0) FlushSlide();
|
||||||
|
sections.Add(current);
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳過 header 前的空白
|
||||||
|
while (i < lines.Length && string.IsNullOrWhiteSpace(lines[i])) i++;
|
||||||
|
|
||||||
|
for (; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
string rawLine = lines[i] ?? "";
|
||||||
|
string line = rawLine.TrimEnd();
|
||||||
|
|
||||||
|
string header;
|
||||||
|
if (IsHeader(line, out header))
|
||||||
|
{
|
||||||
|
FlushSection();
|
||||||
|
current = new Section { Name = header };
|
||||||
|
slideBuffer.Clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current == null) continue; // 忽略 header 之前的散行
|
||||||
|
|
||||||
|
// 支援多個分號;每遇到 ; 或 ; 分頁
|
||||||
|
string remaining = line;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int idx = IndexOfBreak(remaining); // ; 或 ;
|
||||||
|
if (idx == -1)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(remaining))
|
||||||
|
slideBuffer.Add(remaining);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string before = remaining.Substring(0, idx);
|
||||||
|
if (!string.IsNullOrWhiteSpace(before))
|
||||||
|
slideBuffer.Add(before);
|
||||||
|
|
||||||
|
// 分頁
|
||||||
|
FlushSlide();
|
||||||
|
|
||||||
|
// 繼續處理分號之後的內容
|
||||||
|
remaining = remaining.Substring(idx + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收尾
|
||||||
|
FlushSection();
|
||||||
|
|
||||||
|
song.Sections = sections;
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsHeader(string line, out string name)
|
||||||
|
{
|
||||||
|
name = "";
|
||||||
|
if (string.IsNullOrWhiteSpace(line)) return false;
|
||||||
|
line = line.Trim();
|
||||||
|
if (line.Length >= 3 && line.StartsWith("[") && line.EndsWith("]"))
|
||||||
|
{
|
||||||
|
name = line.Substring(1, line.Length - 2).Trim();
|
||||||
|
return name.Length > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int IndexOfBreak(string s)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(s)) return -1;
|
||||||
|
int a = s.IndexOf(';'); // 半形
|
||||||
|
int b = s.IndexOf(';'); // 全形
|
||||||
|
if (a == -1) return b;
|
||||||
|
if (b == -1) return a;
|
||||||
|
return Math.Min(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ContainsCjk(string s)
|
||||||
|
{
|
||||||
|
foreach (char ch in s)
|
||||||
|
{
|
||||||
|
// 基本 CJK 區段(足夠處理歌詞)
|
||||||
|
if (ch >= 0x4E00 && ch <= 0x9FFF) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string MakeSafeFileName(string s)
|
||||||
|
{
|
||||||
|
char[] invalid = Path.GetInvalidFileNameChars();
|
||||||
|
char[] arr = s.Select(c => invalid.Contains(c) ? '_' : c).ToArray();
|
||||||
|
string cleaned = new string(arr);
|
||||||
|
return string.IsNullOrWhiteSpace(cleaned) ? "Presentation" : cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- Pro6 XML building ----------------
|
||||||
|
|
||||||
|
static XDocument BuildPro6(Song song, int width, int height)
|
||||||
|
{
|
||||||
|
XElement root = new XElement("RVPresentationDocument",
|
||||||
|
new XAttribute("height", height),
|
||||||
|
new XAttribute("width", width),
|
||||||
|
new XAttribute("docType", "0"),
|
||||||
|
new XAttribute("versionNumber", "600"),
|
||||||
|
new XAttribute("usedCount", "0"),
|
||||||
|
new XAttribute("backgroundColor", "0 0 0 1"),
|
||||||
|
new XAttribute("drawingBackgroundColor", "false"),
|
||||||
|
new XAttribute("CCLIDisplay", "false"),
|
||||||
|
new XAttribute("lastDateUsed", ""),
|
||||||
|
new XAttribute("selectedArrangementID", ""),
|
||||||
|
new XAttribute("category", "Presentation"),
|
||||||
|
new XAttribute("resourcesDirectory", ""),
|
||||||
|
new XAttribute("notes", ""),
|
||||||
|
new XAttribute("CCLISongTitle", song.TitleZh ?? song.Title),
|
||||||
|
new XAttribute("chordChartPath", ""),
|
||||||
|
new XAttribute("os", "1"),
|
||||||
|
new XAttribute("buildNumber", "6016")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Minimal timeline
|
||||||
|
root.Add(new XElement("RVTimeline",
|
||||||
|
new XAttribute("timeOffset", "0"),
|
||||||
|
new XAttribute("duration", "0"),
|
||||||
|
new XAttribute("selectedMediaTrackIndex", "-1"),
|
||||||
|
new XAttribute("loop", "false"),
|
||||||
|
new XAttribute("rvXMLIvarName", "timeline"),
|
||||||
|
new XElement("array", new XAttribute("rvXMLIvarName", "timeCues")),
|
||||||
|
new XElement("array", new XAttribute("rvXMLIvarName", "mediaTracks"))
|
||||||
|
));
|
||||||
|
|
||||||
|
XElement groupsArray = new XElement("array", new XAttribute("rvXMLIvarName", "groups"));
|
||||||
|
root.Add(groupsArray);
|
||||||
|
|
||||||
|
// Intro group at top
|
||||||
|
groupsArray.Add(BuildIntroGroup(song, width, height));
|
||||||
|
|
||||||
|
// Section groups
|
||||||
|
foreach (Section sec in song.Sections)
|
||||||
|
{
|
||||||
|
XElement group = new XElement("RVSlideGrouping",
|
||||||
|
new XAttribute("name", sec.Name),
|
||||||
|
new XAttribute("color", "1 1 1 0"),
|
||||||
|
new XAttribute("uuid", Guid.NewGuid().ToString())
|
||||||
|
);
|
||||||
|
|
||||||
|
XElement slidesArray = new XElement("array", new XAttribute("rvXMLIvarName", "slides"));
|
||||||
|
group.Add(slidesArray);
|
||||||
|
|
||||||
|
foreach (string slideText in sec.Slides)
|
||||||
|
{
|
||||||
|
// ⬇️ 自動分頁:可能回傳 1..N 張投影片
|
||||||
|
foreach (XElement pageSlide in BuildSlidesAutoPaginate(song, sec.Name, slideText, width, height))
|
||||||
|
{
|
||||||
|
slidesArray.Add(pageSlide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsArray.Add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrangements (empty)
|
||||||
|
root.Add(new XElement("array", new XAttribute("rvXMLIvarName", "arrangements")));
|
||||||
|
|
||||||
|
return new XDocument(new XDeclaration("1.0", "utf-8", null), root);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- Slides & Pagination ----------------
|
||||||
|
|
||||||
|
static XElement BuildIntroGroup(Song song, int width, int height)
|
||||||
|
{
|
||||||
|
XElement group = new XElement("RVSlideGrouping",
|
||||||
|
new XAttribute("name", "Intro"),
|
||||||
|
new XAttribute("color", "1 1 1 0"),
|
||||||
|
new XAttribute("uuid", Guid.NewGuid().ToString())
|
||||||
|
);
|
||||||
|
|
||||||
|
XElement slidesArray = new XElement("array", new XAttribute("rvXMLIvarName", "slides"));
|
||||||
|
group.Add(slidesArray);
|
||||||
|
|
||||||
|
slidesArray.Add(BuildIntroSlide(song, width, height));
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XElement BuildIntroSlide(Song song, int docWidth, int docHeight)
|
||||||
|
{
|
||||||
|
XElement slide = NewEmptySlide();
|
||||||
|
XElement elements = new XElement("array", new XAttribute("rvXMLIvarName", "displayElements"));
|
||||||
|
|
||||||
|
string titleZh = song.TitleZh ?? song.Title;
|
||||||
|
string? titleEn = song.TitleEn;
|
||||||
|
|
||||||
|
int marginX = 80;
|
||||||
|
int width = docWidth - marginX * 2;
|
||||||
|
int gap = 20;
|
||||||
|
|
||||||
|
// 150pt / 105pt → RTF \fs (half-points)
|
||||||
|
int fsCn = 300;
|
||||||
|
int fsEn = 210;
|
||||||
|
|
||||||
|
int cnHeight = 260;
|
||||||
|
int enHeight = 180;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(titleEn))
|
||||||
|
{
|
||||||
|
int totalH = cnHeight + gap + enHeight;
|
||||||
|
int startY = (docHeight - totalH) / 2;
|
||||||
|
|
||||||
|
string cnRect = "{" + marginX + " " + startY + " 0 " + width + " " + cnHeight + "}";
|
||||||
|
elements.Add(BuildTextElement(titleZh, cnRect, fsCn, TextAlign.Center));
|
||||||
|
|
||||||
|
string enRect = "{" + marginX + " " + (startY + cnHeight + gap) + " 0 " + width + " " + enHeight + "}";
|
||||||
|
elements.Add(BuildTextElement(titleEn, enRect, fsEn, TextAlign.Center));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int startY = (docHeight - cnHeight) / 2;
|
||||||
|
string rect = "{" + marginX + " " + startY + " 0 " + width + " " + cnHeight + "}";
|
||||||
|
elements.Add(BuildTextElement(titleZh, rect, fsCn, TextAlign.Center));
|
||||||
|
}
|
||||||
|
|
||||||
|
slide.Add(elements);
|
||||||
|
return slide;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自動分頁:把一段 slideText 依版面自動切成多張投影片
|
||||||
|
static IEnumerable<XElement> BuildSlidesAutoPaginate(Song song, string sectionName, string slideText, int docWidth, int docHeight)
|
||||||
|
{
|
||||||
|
// 版面參數(與單頁版一致)
|
||||||
|
int marginX = 80;
|
||||||
|
int marginTop = 120;
|
||||||
|
int marginBottom = 120;
|
||||||
|
int innerGap = 12; // 同組中英行距
|
||||||
|
int width = docWidth - marginX * 2;
|
||||||
|
|
||||||
|
// 字級(half-points):100pt=200、90pt=180
|
||||||
|
int fsCn = 200;
|
||||||
|
int fsEn = 180;
|
||||||
|
|
||||||
|
// 高度估值(像素)
|
||||||
|
int cnHeight = 190;
|
||||||
|
int enHeight = 160;
|
||||||
|
|
||||||
|
// 一組(中+英)所需高度(含一點額外間距)
|
||||||
|
int pairSlot = cnHeight + innerGap + enHeight + 20;
|
||||||
|
|
||||||
|
// 可用高度
|
||||||
|
int usableHeight = docHeight - marginTop - marginBottom;
|
||||||
|
|
||||||
|
// 先做中英配對
|
||||||
|
List<string> rawLines = (slideText ?? "")
|
||||||
|
.Replace("\r\n", "\n").Replace("\r", "\n")
|
||||||
|
.Split('\n')
|
||||||
|
.Select(s => s.TrimEnd())
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
List<Tuple<string?, string?>> pairs = new List<Tuple<string?, string?>>();
|
||||||
|
for (int i = 0; i < rawLines.Count;)
|
||||||
|
{
|
||||||
|
string line = rawLines[i];
|
||||||
|
if (ContainsCjk(line))
|
||||||
|
{
|
||||||
|
string? en = null;
|
||||||
|
if (i + 1 < rawLines.Count && !ContainsCjk(rawLines[i + 1]))
|
||||||
|
{
|
||||||
|
en = rawLines[i + 1];
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
pairs.Add(new Tuple<string?, string?>(line, en));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pairs.Add(new Tuple<string?, string?>(null, line));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 開始分頁
|
||||||
|
int idx = 0;
|
||||||
|
while (idx < pairs.Count)
|
||||||
|
{
|
||||||
|
XElement slide = NewEmptySlide();
|
||||||
|
XElement elementsArray = new XElement("array", new XAttribute("rvXMLIvarName", "displayElements"));
|
||||||
|
int y = marginTop;
|
||||||
|
|
||||||
|
while (idx < pairs.Count)
|
||||||
|
{
|
||||||
|
string? cn = pairs[idx].Item1;
|
||||||
|
string? en = pairs[idx].Item2;
|
||||||
|
bool isPair = (cn != null && en != null);
|
||||||
|
|
||||||
|
int needed = isPair ? pairSlot : (ContainsCjk(cn ?? en ?? "") ? (cnHeight + 20) : (enHeight + 20));
|
||||||
|
|
||||||
|
// 如果放不下,先結束這一頁
|
||||||
|
if (y + needed > marginTop + usableHeight)
|
||||||
|
{
|
||||||
|
// 若這頁還沒放任何元素,強制塞一個以避免死循環
|
||||||
|
if (y == marginTop)
|
||||||
|
{
|
||||||
|
if (isPair)
|
||||||
|
{
|
||||||
|
string cnRect0 = "{" + marginX + " " + y + " 0 " + width + " " + cnHeight + "}";
|
||||||
|
elementsArray.Add(BuildTextElement(cn!, cnRect0, fsCn, TextAlign.Left));
|
||||||
|
|
||||||
|
string enRect0 = "{" + marginX + " " + (y + cnHeight + innerGap) + " 0 " + width + " " + enHeight + "}";
|
||||||
|
elementsArray.Add(BuildTextElement(en!, enRect0, fsEn, TextAlign.Left));
|
||||||
|
|
||||||
|
y += needed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isCn = ContainsCjk(cn ?? en ?? "");
|
||||||
|
int fs = isCn ? fsCn : fsEn;
|
||||||
|
int boxH = isCn ? cnHeight : enHeight;
|
||||||
|
string rect0 = "{" + marginX + " " + y + " 0 " + width + " " + boxH + "}";
|
||||||
|
elementsArray.Add(BuildTextElement(cn ?? en ?? "", rect0, fs, TextAlign.Left));
|
||||||
|
y += (boxH + 20);
|
||||||
|
}
|
||||||
|
idx++; // 放了一個,移到下一筆
|
||||||
|
}
|
||||||
|
break; // 結束這頁
|
||||||
|
}
|
||||||
|
|
||||||
|
// 放進這頁
|
||||||
|
if (isPair)
|
||||||
|
{
|
||||||
|
string cnRect = "{" + marginX + " " + y + " 0 " + width + " " + cnHeight + "}";
|
||||||
|
elementsArray.Add(BuildTextElement(cn!, cnRect, fsCn, TextAlign.Left));
|
||||||
|
|
||||||
|
string enRect = "{" + marginX + " " + (y + cnHeight + innerGap) + " 0 " + width + " " + enHeight + "}";
|
||||||
|
elementsArray.Add(BuildTextElement(en!, enRect, fsEn, TextAlign.Left));
|
||||||
|
|
||||||
|
y += pairSlot;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isCn = ContainsCjk(cn ?? en ?? "");
|
||||||
|
int fs = isCn ? fsCn : fsEn;
|
||||||
|
int boxH = isCn ? cnHeight : enHeight;
|
||||||
|
|
||||||
|
string rect = "{" + marginX + " " + y + " 0 " + width + " " + boxH + "}";
|
||||||
|
elementsArray.Add(BuildTextElement(cn ?? en ?? "", rect, fs, TextAlign.Left));
|
||||||
|
|
||||||
|
y += (boxH + 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入右下角頁尾
|
||||||
|
string footerText = song.FooterText(sectionName);
|
||||||
|
elementsArray.Add(BuildTextElement(
|
||||||
|
footerText,
|
||||||
|
rect: FooterRect(docWidth, docHeight),
|
||||||
|
fontSizeHalfPoints: 28, // ≈14pt
|
||||||
|
align: TextAlign.Right
|
||||||
|
));
|
||||||
|
|
||||||
|
slide.Add(elementsArray);
|
||||||
|
yield return slide;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static XElement NewEmptySlide()
|
||||||
|
{
|
||||||
|
return new XElement("RVDisplaySlide",
|
||||||
|
new XAttribute("backgroundColor", "1 1 1 0"),
|
||||||
|
new XAttribute("highlightColor", ""),
|
||||||
|
new XAttribute("drawingBackgroundColor", "false"),
|
||||||
|
new XAttribute("enabled", "true"),
|
||||||
|
new XAttribute("hotKey", ""),
|
||||||
|
new XAttribute("label", ""),
|
||||||
|
new XAttribute("notes", ""),
|
||||||
|
new XAttribute("UUID", Guid.NewGuid().ToString()),
|
||||||
|
new XAttribute("chordChartPath", ""),
|
||||||
|
new XElement("array", new XAttribute("rvXMLIvarName", "cues"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TextAlign { Left, Center, Right }
|
||||||
|
|
||||||
|
static string FooterRect(int docWidth, int docHeight)
|
||||||
|
{
|
||||||
|
int pad = 30;
|
||||||
|
int footerWidth = 900; // 雙語標題較長,留寬一點
|
||||||
|
int footerHeight = 50;
|
||||||
|
int x = docWidth - pad - footerWidth;
|
||||||
|
int y = docHeight - pad - footerHeight;
|
||||||
|
return "{" + x + " " + y + " 0 " + footerWidth + " " + footerHeight + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立一個 RVTextElement(RTF 內用白色、Unicode \uNNNN、可選對齊)
|
||||||
|
static XElement BuildTextElement(string text, string rect, int fontSizeHalfPoints, TextAlign align)
|
||||||
|
{
|
||||||
|
string rtf = BuildRtf(text, fontSizeHalfPoints, align);
|
||||||
|
string rtfB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(rtf));
|
||||||
|
string plainB64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(text ?? ""));
|
||||||
|
|
||||||
|
return new XElement("RVTextElement",
|
||||||
|
new XAttribute("displayName", "Text"),
|
||||||
|
new XAttribute("UUID", Guid.NewGuid().ToString()),
|
||||||
|
new XAttribute("typeID", "0"),
|
||||||
|
new XAttribute("displayDelay", "0"),
|
||||||
|
new XAttribute("locked", "false"),
|
||||||
|
new XAttribute("persistent", "0"),
|
||||||
|
new XAttribute("fromTemplate", "false"),
|
||||||
|
new XAttribute("opacity", "1"),
|
||||||
|
new XAttribute("source", ""),
|
||||||
|
new XAttribute("bezelRadius", "0"),
|
||||||
|
new XAttribute("rotation", "0"),
|
||||||
|
new XAttribute("drawingFill", "false"),
|
||||||
|
new XAttribute("drawingShadow", "false"),
|
||||||
|
new XAttribute("drawingStroke", "false"),
|
||||||
|
new XAttribute("fillColor", "1 1 1 1"),
|
||||||
|
new XAttribute("adjustsHeightToFit", "false"),
|
||||||
|
new XAttribute("verticalAlignment", "0"),
|
||||||
|
new XAttribute("revealType", "0"),
|
||||||
|
|
||||||
|
new XElement("RVRect3D",
|
||||||
|
new XAttribute("rvXMLIvarName", "position"),
|
||||||
|
rect
|
||||||
|
),
|
||||||
|
|
||||||
|
new XElement("dictionary", new XAttribute("rvXMLIvarName", "stroke"),
|
||||||
|
new XElement("NSColor",
|
||||||
|
new XAttribute("rvXMLIvarName", "RVShapeElementStrokeColorKey"),
|
||||||
|
"1 1 1 1"
|
||||||
|
),
|
||||||
|
new XElement("NSNumber",
|
||||||
|
new XAttribute("rvXMLIvarName", "RVShapeElementStrokeWidthKey"),
|
||||||
|
new XAttribute("serialization", "double"),
|
||||||
|
"0"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
new XElement("NSString", new XAttribute("rvXMLIvarName", "PlainText"), plainB64),
|
||||||
|
new XElement("NSString", new XAttribute("rvXMLIvarName", "RTFData"), rtfB64)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 產生白字 + Unicode 的 RTF(全 ASCII,非 ASCII 用 \uNNNN)
|
||||||
|
static string BuildRtf(string text, int fontSizeHalfPoints, TextAlign align)
|
||||||
|
{
|
||||||
|
// 將換行先標準化,之後轉成 \line
|
||||||
|
string normalized = (text ?? "").Replace("\r\n", "\n").Replace("\r", "\n");
|
||||||
|
string content = ToRtfUnicode(normalized).Replace("\n", @"\line ");
|
||||||
|
|
||||||
|
string alignCode =
|
||||||
|
align == TextAlign.Right ? @"\qr " :
|
||||||
|
(align == TextAlign.Center ? @"\qc " : @"\ql ");
|
||||||
|
|
||||||
|
// 注意:\fs 是 half-points;\uc0 表示 \u 後不帶替代字元
|
||||||
|
// 字型表:f0=Arial(備用西文字型)、f1=Microsoft JhengHei(CJK 友善)
|
||||||
|
return @"{\rtf1\ansi\deff0"
|
||||||
|
+ @"{\fonttbl{\f0 Arial;}{\f1 Microsoft JhengHei;}}"
|
||||||
|
+ @"{\colortbl ;\red255\green255\blue255;}"
|
||||||
|
+ @"\uc0\f1\fs" + fontSizeHalfPoints + " " + alignCode + @"\cf1 "
|
||||||
|
+ content
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把所有非 ASCII 字元轉成 RTF Unicode:\uNNNN(以 16 位有號整數輸出),並處理 RTF 轉義
|
||||||
|
static string ToRtfUnicode(string s)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder(s.Length * 6);
|
||||||
|
foreach (char ch in s)
|
||||||
|
{
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\\': sb.Append(@"\\"); break;
|
||||||
|
case '{': sb.Append(@"\{"); break;
|
||||||
|
case '}': sb.Append(@"\}"); break;
|
||||||
|
case '\n': sb.Append('\n'); break; // 之後替換成 \line
|
||||||
|
default:
|
||||||
|
if (ch <= 0x7F)
|
||||||
|
{
|
||||||
|
sb.Append(ch);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// \uNNNN 使用 16 位帶正負號的數值
|
||||||
|
sb.Append(@"\u").Append(unchecked((short)ch)).Append(' ');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
PropresentorScript/Properties/AssemblyInfo.cs
Normal file
36
PropresentorScript/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("PropresentorScript")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("PropresentorScript")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("d9c65f50-0828-4e88-9c8f-250a0ab57c98")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
55
PropresentorScript/PropresentorScript.csproj
Normal file
55
PropresentorScript/PropresentorScript.csproj
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<LangVersion>9.0</LangVersion>
|
||||||
|
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{D9C65F50-0828-4E88-9C8F-250A0AB57C98}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>PropresentorScript</RootNamespace>
|
||||||
|
<AssemblyName>PropresentorScript</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
19
TestProject1/TestProject1.csproj
Normal file
19
TestProject1/TestProject1.csproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
16
TestProject1/UnitTest1.cs
Normal file
16
TestProject1/UnitTest1.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace TestProject1
|
||||||
|
{
|
||||||
|
public class Tests
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
Assert.Pass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
TestProject1/Usings.cs
Normal file
1
TestProject1/Usings.cs
Normal file
@ -0,0 +1 @@
|
|||||||
|
global using NUnit.Framework;
|
||||||
Loading…
x
Reference in New Issue
Block a user